# HG changeset patch # User Bradley M. Kuhn # Date 1404342193 14400 # Node ID ffd45b1850168070e2d1d6713e39ea8b2607f9f2 # Parent 8b7294a804a09f05343114eec6e066818f5cce87 Imported some of the GPLv3'd changes from RhodeCode v2.2.5. This imports changes between changesets 21af6c4eab3d and 6177597791c2 in RhodeCode's original repository, including only changes to Python files and HTML. RhodeCode clearly licensed its changes to these files under GPLv3 in their /LICENSE file, which states the following: The Python code and integrated HTML are licensed under the GPLv3 license. (See: https://code.rhodecode.com/rhodecode/files/v2.2.5/LICENSE or http://web.archive.org/web/20140512193334/https://code.rhodecode.com/rhodecode/files/f3b123159901f15426d18e3dc395e8369f70ebe0/LICENSE for an online copy of that LICENSE file) Conservancy reviewed these changes and confirmed that they can be licensed as a whole to the Kallithea project under GPLv3-only. While some of the contents committed herein are clearly licensed GPLv3-or-later, on the whole we must assume the are GPLv3-only, since the statement above from RhodeCode indicates that they intend GPLv3-only as their license, per GPLv3ยง14 and other relevant sections of GPLv3. diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/__init__.py --- a/rhodecode/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.__init__ - ~~~~~~~~~~~~~~~~~~ - - RhodeCode, a web based repository management based on pylons - versioning implementation: http://www.python.org/dev/peps/pep-0386/ - - :created_on: Apr 9, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,10 +11,23 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.__init__ +~~~~~~~~~~~~~~~~~~ + +RhodeCode, a web based repository management based on pylons +versioning implementation: http://www.python.org/dev/peps/pep-0386/ + +:created_on: Apr 9, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import sys import platform -VERSION = (1, 7, 1) +VERSION = (2, 2, 5) BACKENDS = { 'hg': 'Mercurial repository', 'git': 'Git repository', @@ -49,14 +50,21 @@ except ImportError: pass -__version__ = ('.'.join((str(each) for each in VERSION[:3])) + - '.'.join(VERSION[3:])) -__dbversion__ = 14 # defines current db version for migrations +__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__ = 'Marcin Kuzminski' -__url__ = 'http://rhodecode.org' +__author__ = 'RhodeCode GmbH' +__url__ = 'http://rhodecode.com' 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' diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/bin/__init__.py --- a/rhodecode/bin/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/bin/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,24 @@ +# -*- 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 . +""" +rhodecode.bin.__init__ +~~~~~~~~~~~~~~~~~~~~~~ + +Binary scripts for RhodeCode + +:created_on: Jun 03, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/bin/base.py --- a/rhodecode/bin/base.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/bin/base.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,6 +1,28 @@ +# -*- 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 . """ +rhodecode.bin.base +~~~~~~~~~~~~~~~~~~ + Base utils for shell scripts + +:created_on: May 09, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. """ + import os import sys import random @@ -46,7 +68,7 @@ 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_)), diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/bin/ldap_sync.py --- a/rhodecode/bin/ldap_sync.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/bin/ldap_sync.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,3 +1,4 @@ +# -*- 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 @@ -10,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.bin.__init__ +~~~~~~~~~~~~~~~~~~~~~~ + +LDAP sync script + +:created_on: Mar 06, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import ldap import urllib2 @@ -45,7 +57,7 @@ """ User is not a member of the target group. """ -class RhodecodeAPI(): +class RhodecodeAPI(object): def __init__(self, url, key): self.url = url @@ -90,7 +102,7 @@ "group_name": name, "active": str(active) } - self.rhodecode_api_post("create_users_group", args) + self.rhodecode_api_post("create_user_group", args) def add_membership(self, group, username): """Add specific user to a group.""" @@ -98,7 +110,7 @@ "usersgroupid": group, "userid": username } - result = self.rhodecode_api_post("add_user_to_users_group", args) + result = self.rhodecode_api_post("add_user_to_user_group", args) if not result["success"]: raise UserAlreadyInGroupError("User %s already in group %s." % (username, group)) @@ -109,7 +121,7 @@ "usersgroupid": group, "userid": username } - result = self.rhodecode_api_post("remove_user_from_users_group", args) + result = self.rhodecode_api_post("remove_user_from_user_group", args) if not result["success"]: raise UserNotInGroupError("User %s not in group %s." % (username, group)) @@ -117,7 +129,7 @@ def get_group_members(self, name): """Get the list of member usernames from a user group.""" args = {"usersgroupid": name} - members = self.rhodecode_api_post("get_users_group", args)['members'] + members = self.rhodecode_api_post("get_user_group", args)['members'] member_list = [] for member in members: member_list.append(member["username"]) @@ -126,7 +138,7 @@ def get_group(self, name): """Return group info.""" args = {"usersgroupid": name} - return self.rhodecode_api_post("get_users_group", args) + return self.rhodecode_api_post("get_user_group", args) def get_user(self, username): """Return user info.""" @@ -134,7 +146,7 @@ return self.rhodecode_api_post("get_user", args) -class LdapClient(): +class LdapClient(object): def __init__(self, uri, user, key, base_dn): self.client = ldap.initialize(uri, trace_level=0) @@ -199,7 +211,7 @@ groups = self.ldap_client.get_groups() for group in groups: try: - self.rhodecode_api.create_group(group) + self.rhodecode_api.create_repo_group(group) added += 1 except Exception: existing += 1 diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/bin/rhodecode_api.py --- a/rhodecode/bin/rhodecode_api.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/bin/rhodecode_api.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.bin.api - ~~~~~~~~~~~~~~~~~ - - Api CLI client for RhodeCode - - :created_on: Jun 3, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.bin.api +~~~~~~~~~~~~~~~~~ + +Api CLI client for RhodeCode + +:created_on: Jun 3, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" from __future__ import with_statement import sys @@ -35,7 +35,7 @@ "rhodecode-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] " "[--config=CONFIG] [--save-config] " "METHOD ...\n" - "Create config file: rhodecode-gist --apikey= --apihost=http://rhodecode.server --save-config" + "Create config file: rhodecode-api --apikey= --apihost=http://rhodecode.server --save-config" ) parser = argparse.ArgumentParser(description='RhodeCode API cli', @@ -106,15 +106,18 @@ 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' % ( - json.dumps(json_data, indent=4, sort_keys=True)) + print 'Server response \n%s%s' % ( + error_prefix, json.dumps(json_data, indent=4, sort_keys=True) + ) return 0 if __name__ == '__main__': diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/bin/rhodecode_backup.py --- a/rhodecode/bin/rhodecode_backup.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/bin/rhodecode_backup.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.bin.backup_manager - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Repositories backup manager, it allows to backups all - repositories and send it to backup server using RSA key via ssh. - - :created_on: Feb 28, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.bin.backup_manager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Repositories backup manager, it allows to backups all +repositories and send it to backup server using RSA key via ssh. + +:created_on: Feb 28, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import sys diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/bin/rhodecode_config.py --- a/rhodecode/bin/rhodecode_config.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/bin/rhodecode_config.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,7 +1,29 @@ +# -*- 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 . """ -config generator +rhodecode.bin.rhodecode_config +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +configuration generator for RhodeCode +:created_on: Jun 18, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. """ + + from __future__ import with_statement import os import sys @@ -71,13 +93,16 @@ print parser.print_help() sys.exit(0) # defaults that can be overwritten by arguments + from rhodecode.model.license import LicenseModel + license_token = LicenseModel.generate_license_token() tmpl_stored_args = { 'http_server': 'waitress', 'lang': 'en', 'database_engine': 'sqlite', 'host': '127.0.0.1', 'port': 5000, - 'error_aggregation_service': None + 'error_aggregation_service': None, + 'license_token': license_token } if other: # parse arguments, we assume only first is correct diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/bin/rhodecode_gist.py --- a/rhodecode/bin/rhodecode_gist.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/bin/rhodecode_gist.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.bin.gist - ~~~~~~~~~~~~~~~~~~ - - Gist CLI client for RhodeCode - - :created_on: May 9, 2013 - :author: marcink - :copyright: (C) 2010-2013 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.bin.rhodecode_gist +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Gist CLI client for RhodeCode + +:created_on: May 9, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" from __future__ import with_statement import os diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/config/__init__.py --- a/rhodecode/config/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/config/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,13 @@ +# -*- 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 . diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/config/conf.py --- a/rhodecode/config/conf.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/config/conf.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,14 +1,26 @@ # -*- 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 . """ - package.rhodecode.config.conf - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +rhodecode.config.conf +~~~~~~~~~~~~~~~~~~~~~ - Various config settings for RhodeCode +Various config settings for RhodeCode - :created_on: Mar 7, 2012 - :author: marcink - :copyright: (C) 2009-2010 Marcin Kuzminski - :license: , see LICENSE_FILE for more details. +:created_on: Mar 7, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. """ from rhodecode import EXTENSIONS diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/config/environment.py --- a/rhodecode/config/environment.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/config/environment.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,8 +1,24 @@ -"""Pylons environment configuration""" +# -*- 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 . +""" + Pylons environment configuration +""" import os import logging import rhodecode +import platform from mako.lookup import TemplateLookup from pylons.configuration import PylonsConfig @@ -27,7 +43,8 @@ log = logging.getLogger(__name__) -def load_environment(global_conf, app_conf, initial=False): +def load_environment(global_conf, app_conf, initial=False, + test_env=None, test_index=None): """ Configure the Pylons environment via the ``pylons.config`` object @@ -73,18 +90,22 @@ config['pylons.strict_tmpl_context'] = True test = os.path.split(config['__file__'])[-1] == 'test.ini' if test: + if test_env is None: + test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0)) + if test_index is None: + test_index = not int(os.environ.get('RC_WHOOSH_TEST_DISABLE', 0)) if os.environ.get('TEST_DB'): # swap config if we pass enviroment variable config['sqlalchemy.db1.url'] = os.environ.get('TEST_DB') from rhodecode.lib.utils import create_test_env, create_test_index - from rhodecode.tests import TESTS_TMP_PATH - # set RC_NO_TMP_PATH=1 to disable re-creating the database and - # test repos - if not int(os.environ.get('RC_NO_TMP_PATH', 0)): + from rhodecode.tests import TESTS_TMP_PATH + #set RC_NO_TMP_PATH=1 to disable re-creating the database and + #test repos + if test_env: create_test_env(TESTS_TMP_PATH, config) - # set RC_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests - if not int(os.environ.get('RC_WHOOSH_TEST_DISABLE', 0)): + #set RC_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests + if test_index: create_test_index(TESTS_TMP_PATH, config, True) DbManage.check_waitress() @@ -100,7 +121,7 @@ instance_id = rhodecode.CONFIG.get('instance_id') if instance_id == '*': - instance_id = '%s-%s' % (os.uname()[1], os.getpid()) + instance_id = '%s-%s' % (platform.uname()[1], os.getpid()) rhodecode.CONFIG['instance_id'] = instance_id # CONFIGURATION OPTIONS HERE (note: all config options will override diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/config/middleware.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,4 +1,19 @@ -"""Pylons middleware initialization""" +# -*- 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 . +""" + Pylons middleware initialization +""" from beaker.middleware import SessionMiddleware from routes.middleware import RoutesMiddleware diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/config/post_receive_tmpl.py --- a/rhodecode/config/post_receive_tmpl.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/config/post_receive_tmpl.py Wed Jul 02 19:03:13 2014 -0400 @@ -6,8 +6,11 @@ import rhodecode RC_HOOK_VER = '_TMPL_' os.environ['RC_HOOK_VER'] = RC_HOOK_VER - from rhodecode.lib.hooks import handle_git_post_receive + from rhodecode.lib.hooks import handle_git_post_receive as _handler except ImportError: + if os.environ.get('RC_DEBUG_GIT_HOOK'): + import traceback + print traceback.format_exc() rhodecode = None @@ -24,7 +27,7 @@ # runs git and later git executes this hook. # Environ gets some additional info from rhodecode system # like IP or username from basic-auth - handle_git_post_receive(repo_path, push_data, os.environ) + _handler(repo_path, push_data, os.environ) sys.exit(0) if __name__ == '__main__': diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/config/pre_receive_tmpl.py --- a/rhodecode/config/pre_receive_tmpl.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/config/pre_receive_tmpl.py Wed Jul 02 19:03:13 2014 -0400 @@ -6,8 +6,11 @@ import rhodecode RC_HOOK_VER = '_TMPL_' os.environ['RC_HOOK_VER'] = RC_HOOK_VER - from rhodecode.lib.hooks import handle_git_pre_receive + from rhodecode.lib.hooks import handle_git_pre_receive as _handler except ImportError: + if os.environ.get('RC_DEBUG_GIT_HOOK'): + import traceback + print traceback.format_exc() rhodecode = None @@ -24,7 +27,7 @@ # runs git and later git executes this hook. # Environ gets some additional info from rhodecode system # like IP or username from basic-auth - handle_git_pre_receive(repo_path, push_data, os.environ) + _handler(repo_path, push_data, os.environ) sys.exit(0) if __name__ == '__main__': diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/config/routing.py --- a/rhodecode/config/routing.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/config/routing.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,3 +1,16 @@ +# -*- 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 . """ Routes configuration @@ -5,6 +18,7 @@ may take precedent over the more generic routes. For more information refer to the routes manual at http://routes.groovie.org/docs/ """ + from __future__ import with_statement from routes import Mapper @@ -15,12 +29,12 @@ def make_map(config): """Create, configure and return the routes Mapper""" rmap = Mapper(directory=config['pylons.paths']['controllers'], - always_scan=config['debug']) + always_scan=config['debug']) rmap.minimization = False rmap.explicit = False - from rhodecode.lib.utils import is_valid_repo - from rhodecode.lib.utils import is_valid_repos_group + from rhodecode.lib.utils import (is_valid_repo, is_valid_repo_group, + get_repo_by_id) def check_repo(environ, match_dict): """ @@ -29,20 +43,16 @@ :param environ: :param match_dict: """ - from rhodecode.model.db import Repository repo_name = match_dict.get('repo_name') if match_dict.get('f_path'): #fix for multiple initial slashes that causes errors match_dict['f_path'] = match_dict['f_path'].lstrip('/') - try: - by_id = repo_name.split('_') - if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '': - repo_name = Repository.get(by_id[1]).repo_name - match_dict['repo_name'] = repo_name - except Exception: - pass + by_id_match = get_repo_by_id(repo_name) + if by_id_match: + repo_name = by_id_match + match_dict['repo_name'] = repo_name return is_valid_repo(repo_name, config['base_path']) @@ -53,8 +63,8 @@ :param environ: :param match_dict: """ - repos_group_name = match_dict.get('group_name') - return is_valid_repos_group(repos_group_name, config['base_path']) + repo_group_name = match_dict.get('group_name') + return is_valid_repo_group(repo_group_name, config['base_path']) def check_group_skip_path(environ, match_dict): """ @@ -64,9 +74,9 @@ :param environ: :param match_dict: """ - repos_group_name = match_dict.get('group_name') - return is_valid_repos_group(repos_group_name, config['base_path'], - skip_path_check=True) + repo_group_name = match_dict.get('group_name') + return is_valid_repo_group(repo_group_name, config['base_path'], + skip_path_check=True) def check_user_group(environ, match_dict): """ @@ -91,131 +101,82 @@ #MAIN PAGE rmap.connect('home', '/', controller='home', action='index') - rmap.connect('repo_switcher', '/repos', controller='home', - action='repo_switcher') - rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}', - controller='home', action='branch_tag_switcher') + rmap.connect('repo_switcher_data', '/_repos', controller='home', + action='repo_switcher_data') + rmap.connect('rst_help', "http://docutils.sourceforge.net/docs/user/rst/quickref.html", _static=True) - rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True) + rmap.connect('rhodecode_official', "https://rhodecode.com", _static=True) + rmap.connect('rc_issue_tracker', 'https://rhodecode.com/help/', _static=True) - #ADMIN REPOSITORY REST ROUTES + #ADMIN REPOSITORY ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/repos') as m: m.connect("repos", "/repos", - action="create", conditions=dict(method=["POST"])) + action="create", conditions=dict(method=["POST"])) m.connect("repos", "/repos", - action="index", conditions=dict(method=["GET"])) - m.connect("formatted_repos", "/repos.{format}", - action="index", - conditions=dict(method=["GET"])) + action="index", conditions=dict(method=["GET"])) m.connect("new_repo", "/create_repository", action="create_repository", conditions=dict(method=["GET"])) m.connect("/repos/{repo_name:.*?}", - action="update", conditions=dict(method=["PUT"], - function=check_repo)) - m.connect("/repos/{repo_name:.*?}", - action="delete", conditions=dict(method=["DELETE"], - function=check_repo)) - m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit", - action="edit", conditions=dict(method=["GET"], - function=check_repo)) + action="update", conditions=dict(method=["PUT"], + function=check_repo)) + m.connect("delete_repo", "/repos/{repo_name:.*?}", + action="delete", conditions=dict(method=["DELETE"], + )) m.connect("repo", "/repos/{repo_name:.*?}", - action="show", conditions=dict(method=["GET"], - function=check_repo)) - m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}", - action="show", conditions=dict(method=["GET"], - function=check_repo)) - #add repo perm member - m.connect('set_repo_perm_member', - "/repos/{repo_name:.*?}/grant_perm", - action="set_repo_perm_member", - conditions=dict(method=["POST"], function=check_repo)) - - #ajax delete repo perm user - m.connect('delete_repo_perm_member', - "/repos/{repo_name:.*?}/revoke_perm", - action="delete_repo_perm_member", - conditions=dict(method=["DELETE"], function=check_repo)) + action="show", conditions=dict(method=["GET"], + function=check_repo)) - #settings actions - m.connect('repo_stats', "/repos_stats/{repo_name:.*?}", - action="repo_stats", conditions=dict(method=["DELETE"], - function=check_repo)) - m.connect('repo_cache', "/repos_cache/{repo_name:.*?}", - action="repo_cache", conditions=dict(method=["DELETE"], - function=check_repo)) - m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}", - action="repo_public_journal", conditions=dict(method=["PUT"], - function=check_repo)) - m.connect('repo_pull', "/repo_pull/{repo_name:.*?}", - action="repo_pull", conditions=dict(method=["PUT"], - function=check_repo)) - m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}", - action="repo_as_fork", conditions=dict(method=["PUT"], - function=check_repo)) - m.connect('repo_locking', "/repo_locking/{repo_name:.*?}", - action="repo_locking", conditions=dict(method=["PUT"], - function=check_repo)) - m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}", - action="toggle_locking", conditions=dict(method=["GET"], - function=check_repo)) - - #repo fields - m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new", - action="create_repo_field", conditions=dict(method=["PUT"], - function=check_repo)) - - m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}", - action="delete_repo_field", conditions=dict(method=["DELETE"], - function=check_repo)) - + #ADMIN REPOSITORY GROUPS ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, - controller='admin/repos_groups') as m: - m.connect("repos_groups", "/repos_groups", + controller='admin/repo_groups') as m: + m.connect("repos_groups", "/repo_groups", action="create", conditions=dict(method=["POST"])) - m.connect("repos_groups", "/repos_groups", + m.connect("repos_groups", "/repo_groups", action="index", conditions=dict(method=["GET"])) - m.connect("formatted_repos_groups", "/repos_groups.{format}", - action="index", conditions=dict(method=["GET"])) - m.connect("new_repos_group", "/repos_groups/new", + m.connect("new_repos_group", "/repo_groups/new", action="new", conditions=dict(method=["GET"])) - m.connect("formatted_new_repos_group", "/repos_groups/new.{format}", - action="new", conditions=dict(method=["GET"])) - m.connect("update_repos_group", "/repos_groups/{group_name:.*?}", + m.connect("update_repos_group", "/repo_groups/{group_name:.*?}", action="update", conditions=dict(method=["PUT"], function=check_group)) - #add repo group perm member - m.connect('set_repo_group_perm_member', - "/repos_groups/{group_name:.*?}/grant_perm", - action="set_repo_group_perm_member", - conditions=dict(method=["POST"], function=check_group)) - #ajax delete repo group perm - m.connect('delete_repo_group_perm_member', - "/repos_groups/{group_name:.*?}/revoke_perm", - action="delete_repo_group_perm_member", - conditions=dict(method=["DELETE"], function=check_group)) - - m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}", - action="delete", conditions=dict(method=["DELETE"], - function=check_group_skip_path)) - m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit", - action="edit", conditions=dict(method=["GET"], - function=check_group)) - m.connect("formatted_edit_repos_group", - "/repos_groups/{group_name:.*?}.{format}/edit", - action="edit", conditions=dict(method=["GET"], - function=check_group)) - m.connect("repos_group", "/repos_groups/{group_name:.*?}", - action="show", conditions=dict(method=["GET"], - function=check_group)) - m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}", + m.connect("repos_group", "/repo_groups/{group_name:.*?}", action="show", conditions=dict(method=["GET"], function=check_group)) - #ADMIN USER REST ROUTES + #EXTRAS REPO GROUP ROUTES + m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit", + action="edit", + conditions=dict(method=["GET"], function=check_group)) + m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit", + action="edit", + conditions=dict(method=["PUT"], function=check_group)) + + m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced", + action="edit_repo_group_advanced", + conditions=dict(method=["GET"], function=check_group)) + m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced", + action="edit_repo_group_advanced", + conditions=dict(method=["PUT"], function=check_group)) + + m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions", + action="edit_repo_group_perms", + conditions=dict(method=["GET"], function=check_group)) + m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions", + action="update_perms", + conditions=dict(method=["PUT"], function=check_group)) + m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions", + action="delete_perms", + conditions=dict(method=["DELETE"], function=check_group)) + + m.connect("delete_repos_group", "/repo_groups/{group_name:.*?}", + action="delete", conditions=dict(method=["DELETE"], + function=check_group_skip_path)) + + + #ADMIN USER ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/users') as m: m.connect("users", "/users", @@ -226,131 +187,209 @@ action="index", conditions=dict(method=["GET"])) m.connect("new_user", "/users/new", action="new", conditions=dict(method=["GET"])) - m.connect("formatted_new_user", "/users/new.{format}", - action="new", conditions=dict(method=["GET"])) m.connect("update_user", "/users/{id}", action="update", conditions=dict(method=["PUT"])) m.connect("delete_user", "/users/{id}", action="delete", conditions=dict(method=["DELETE"])) m.connect("edit_user", "/users/{id}/edit", action="edit", conditions=dict(method=["GET"])) - m.connect("formatted_edit_user", - "/users/{id}.{format}/edit", - action="edit", conditions=dict(method=["GET"])) m.connect("user", "/users/{id}", action="show", conditions=dict(method=["GET"])) - m.connect("formatted_user", "/users/{id}.{format}", - action="show", conditions=dict(method=["GET"])) #EXTRAS USER ROUTES - m.connect("user_perm", "/users_perm/{id}", - action="update_perm", conditions=dict(method=["PUT"])) - m.connect("user_emails", "/users_emails/{id}", + m.connect("edit_user_advanced", "/users/{id}/edit/advanced", + action="edit_advanced", conditions=dict(method=["GET"])) + m.connect("edit_user_advanced", "/users/{id}/edit/advanced", + action="update_advanced", conditions=dict(method=["PUT"])) + + m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys", + action="edit_api_keys", conditions=dict(method=["GET"])) + m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys", + action="add_api_key", conditions=dict(method=["PUT"])) + m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys", + action="delete_api_key", conditions=dict(method=["DELETE"])) + + m.connect("edit_user_perms", "/users/{id}/edit/permissions", + action="edit_perms", conditions=dict(method=["GET"])) + m.connect("edit_user_perms", "/users/{id}/edit/permissions", + action="update_perms", conditions=dict(method=["PUT"])) + + m.connect("edit_user_emails", "/users/{id}/edit/emails", + action="edit_emails", conditions=dict(method=["GET"])) + m.connect("edit_user_emails", "/users/{id}/edit/emails", action="add_email", conditions=dict(method=["PUT"])) - m.connect("user_emails_delete", "/users_emails/{id}", + m.connect("edit_user_emails", "/users/{id}/edit/emails", action="delete_email", conditions=dict(method=["DELETE"])) - m.connect("user_ips", "/users_ips/{id}", + + m.connect("edit_user_ips", "/users/{id}/edit/ips", + action="edit_ips", conditions=dict(method=["GET"])) + m.connect("edit_user_ips", "/users/{id}/edit/ips", action="add_ip", conditions=dict(method=["PUT"])) - m.connect("user_ips_delete", "/users_ips/{id}", + m.connect("edit_user_ips", "/users/{id}/edit/ips", action="delete_ip", conditions=dict(method=["DELETE"])) #ADMIN USER GROUPS REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, - controller='admin/users_groups') as m: - m.connect("users_groups", "/users_groups", + controller='admin/user_groups') as m: + m.connect("users_groups", "/user_groups", action="create", conditions=dict(method=["POST"])) - m.connect("users_groups", "/users_groups", - action="index", conditions=dict(method=["GET"])) - m.connect("formatted_users_groups", "/users_groups.{format}", + m.connect("users_groups", "/user_groups", action="index", conditions=dict(method=["GET"])) - m.connect("new_users_group", "/users_groups/new", - action="new", conditions=dict(method=["GET"])) - m.connect("formatted_new_users_group", "/users_groups/new.{format}", + m.connect("new_users_group", "/user_groups/new", action="new", conditions=dict(method=["GET"])) - m.connect("update_users_group", "/users_groups/{id}", + m.connect("update_users_group", "/user_groups/{id}", action="update", conditions=dict(method=["PUT"])) - m.connect("delete_users_group", "/users_groups/{id}", + m.connect("delete_users_group", "/user_groups/{id}", action="delete", conditions=dict(method=["DELETE"])) - m.connect("edit_users_group", "/users_groups/{id}/edit", + m.connect("edit_users_group", "/user_groups/{id}/edit", action="edit", conditions=dict(method=["GET"]), function=check_user_group) - m.connect("formatted_edit_users_group", - "/users_groups/{id}.{format}/edit", - action="edit", conditions=dict(method=["GET"])) - m.connect("users_group", "/users_groups/{id}", - action="show", conditions=dict(method=["GET"])) - m.connect("formatted_users_group", "/users_groups/{id}.{format}", + m.connect("users_group", "/user_groups/{id}", action="show", conditions=dict(method=["GET"])) - #EXTRAS USER ROUTES - # update - m.connect("users_group_perm", "/users_groups/{id}/update_global_perm", - action="update_perm", conditions=dict(method=["PUT"])) + #EXTRAS USER GROUP ROUTES + m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms", + action="edit_default_perms", conditions=dict(method=["GET"])) + m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms", + action="update_default_perms", conditions=dict(method=["PUT"])) + - #add user group perm member - m.connect('set_user_group_perm_member', "/users_groups/{id}/grant_perm", - action="set_user_group_perm_member", - conditions=dict(method=["POST"])) + m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms", + action="edit_perms", conditions=dict(method=["GET"])) + m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms", + action="update_perms", conditions=dict(method=["PUT"])) + m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms", + action="delete_perms", conditions=dict(method=["DELETE"])) + + m.connect("edit_user_group_advanced", "/user_groups/{id}/edit/advanced", + action="edit_advanced", conditions=dict(method=["GET"])) + + m.connect("edit_user_group_members", "/user_groups/{id}/edit/members", + action="edit_members", conditions=dict(method=["GET"])) - #ajax delete user group perm - m.connect('delete_user_group_perm_member', "/users_groups/{id}/revoke_perm", - action="delete_user_group_perm_member", - conditions=dict(method=["DELETE"])) + + + #ADMIN PERMISSIONS ROUTES + with rmap.submapper(path_prefix=ADMIN_PREFIX, + controller='admin/permissions') as m: + m.connect("admin_permissions", "/permissions", + action="permission_globals", conditions=dict(method=["POST"])) + m.connect("admin_permissions", "/permissions", + action="permission_globals", conditions=dict(method=["GET"])) - #ADMIN GROUP REST ROUTES - rmap.resource('group', 'groups', - controller='admin/groups', path_prefix=ADMIN_PREFIX) + m.connect("admin_permissions_ips", "/permissions/ips", + action="permission_ips", conditions=dict(method=["POST"])) + m.connect("admin_permissions_ips", "/permissions/ips", + action="permission_ips", conditions=dict(method=["GET"])) - #ADMIN PERMISSIONS REST ROUTES - rmap.resource('permission', 'permissions', - controller='admin/permissions', path_prefix=ADMIN_PREFIX) + m.connect("admin_permissions_perms", "/permissions/perms", + action="permission_perms", conditions=dict(method=["POST"])) + m.connect("admin_permissions_perms", "/permissions/perms", + action="permission_perms", conditions=dict(method=["GET"])) + #ADMIN DEFAULTS REST ROUTES rmap.resource('default', 'defaults', controller='admin/defaults', path_prefix=ADMIN_PREFIX) - ##ADMIN LDAP SETTINGS - rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX, - controller='admin/ldap_settings', action='ldap_settings', + #ADMIN AUTH SETTINGS + rmap.connect('auth_settings', '%s/auth' % ADMIN_PREFIX, + controller='admin/auth_settings', action='auth_settings', conditions=dict(method=["POST"])) + rmap.connect('auth_home', '%s/auth' % ADMIN_PREFIX, + controller='admin/auth_settings') - rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX, - controller='admin/ldap_settings') - - #ADMIN SETTINGS REST ROUTES + #ADMIN SETTINGS ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/settings') as m: m.connect("admin_settings", "/settings", - action="create", conditions=dict(method=["POST"])) + action="settings_vcs", conditions=dict(method=["POST"])) m.connect("admin_settings", "/settings", - action="index", conditions=dict(method=["GET"])) - m.connect("formatted_admin_settings", "/settings.{format}", - action="index", conditions=dict(method=["GET"])) - m.connect("admin_new_setting", "/settings/new", - action="new", conditions=dict(method=["GET"])) - m.connect("formatted_admin_new_setting", "/settings/new.{format}", - action="new", conditions=dict(method=["GET"])) - m.connect("/settings/{setting_id}", - action="update", conditions=dict(method=["PUT"])) - m.connect("/settings/{setting_id}", - action="delete", conditions=dict(method=["DELETE"])) - m.connect("admin_edit_setting", "/settings/{setting_id}/edit", - action="edit", conditions=dict(method=["GET"])) - m.connect("formatted_admin_edit_setting", - "/settings/{setting_id}.{format}/edit", - action="edit", conditions=dict(method=["GET"])) - m.connect("admin_setting", "/settings/{setting_id}", - action="show", conditions=dict(method=["GET"])) - m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}", - action="show", conditions=dict(method=["GET"])) - m.connect("admin_settings_my_account", "/my_account", + action="settings_vcs", conditions=dict(method=["GET"])) + + m.connect("admin_settings_mapping", "/settings/mapping", + action="settings_mapping", conditions=dict(method=["POST"])) + m.connect("admin_settings_mapping", "/settings/mapping", + action="settings_mapping", conditions=dict(method=["GET"])) + + m.connect("admin_settings_global", "/settings/global", + action="settings_global", conditions=dict(method=["POST"])) + m.connect("admin_settings_global", "/settings/global", + action="settings_global", conditions=dict(method=["GET"])) + + m.connect("admin_settings_visual", "/settings/visual", + action="settings_visual", conditions=dict(method=["POST"])) + m.connect("admin_settings_visual", "/settings/visual", + action="settings_visual", conditions=dict(method=["GET"])) + + m.connect("admin_settings_email", "/settings/email", + action="settings_email", conditions=dict(method=["POST"])) + m.connect("admin_settings_email", "/settings/email", + action="settings_email", conditions=dict(method=["GET"])) + + m.connect("admin_settings_hooks", "/settings/hooks", + action="settings_hooks", conditions=dict(method=["POST"])) + m.connect("admin_settings_hooks", "/settings/hooks", + action="settings_hooks", conditions=dict(method=["DELETE"])) + m.connect("admin_settings_hooks", "/settings/hooks", + action="settings_hooks", conditions=dict(method=["GET"])) + + m.connect("admin_settings_search", "/settings/search", + action="settings_search", conditions=dict(method=["POST"])) + m.connect("admin_settings_search", "/settings/search", + action="settings_search", conditions=dict(method=["GET"])) + + m.connect("admin_settings_system", "/settings/system", + action="settings_system", conditions=dict(method=["POST"])) + m.connect("admin_settings_system", "/settings/system", + action="settings_system", conditions=dict(method=["GET"])) + m.connect("admin_settings_system_update", "/settings/system/updates", + action="settings_system_update", conditions=dict(method=["GET"])) + + m.connect("admin_settings_license", "/settings/license", + action="settings_license", conditions=dict(method=["POST"])) + m.connect("admin_settings_license", "/settings/license", + action="settings_license", conditions=dict(method=["GET"])) + + #ADMIN MY ACCOUNT + with rmap.submapper(path_prefix=ADMIN_PREFIX, + controller='admin/my_account') as m: + + m.connect("my_account", "/my_account", action="my_account", conditions=dict(method=["GET"])) - m.connect("admin_settings_my_account_update", "/my_account_update", - action="my_account_update", conditions=dict(method=["PUT"])) - m.connect("admin_settings_my_repos", "/my_account/repos", - action="my_account_my_repos", conditions=dict(method=["GET"])) - m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests", - action="my_account_my_pullrequests", conditions=dict(method=["GET"])) + m.connect("my_account", "/my_account", + action="my_account", conditions=dict(method=["POST"])) + + m.connect("my_account_password", "/my_account/password", + action="my_account_password", conditions=dict(method=["GET"])) + m.connect("my_account_password", "/my_account/password", + action="my_account_password", conditions=dict(method=["POST"])) + + m.connect("my_account_repos", "/my_account/repos", + action="my_account_repos", conditions=dict(method=["GET"])) + + m.connect("my_account_watched", "/my_account/watched", + action="my_account_watched", conditions=dict(method=["GET"])) + + m.connect("my_account_pullrequests", "/my_account/pull_requests", + action="my_account_pullrequests", conditions=dict(method=["GET"])) + + m.connect("my_account_perms", "/my_account/perms", + action="my_account_perms", conditions=dict(method=["GET"])) + + m.connect("my_account_emails", "/my_account/emails", + action="my_account_emails", conditions=dict(method=["GET"])) + m.connect("my_account_emails", "/my_account/emails", + action="my_account_emails_add", conditions=dict(method=["POST"])) + m.connect("my_account_emails", "/my_account/emails", + action="my_account_emails_delete", conditions=dict(method=["DELETE"])) + + m.connect("my_account_api_keys", "/my_account/api_keys", + action="my_account_api_keys", conditions=dict(method=["GET"])) + m.connect("my_account_api_keys", "/my_account/api_keys", + action="my_account_api_keys_add", conditions=dict(method=["POST"])) + m.connect("my_account_api_keys", "/my_account/api_keys", + action="my_account_api_keys_delete", conditions=dict(method=["DELETE"])) #NOTIFICATION REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, @@ -390,24 +429,28 @@ action="index", conditions=dict(method=["GET"])) m.connect("new_gist", "/gists/new", action="new", conditions=dict(method=["GET"])) - m.connect("formatted_new_gist", "/gists/new.{format}", - action="new", conditions=dict(method=["GET"])) - m.connect("formatted_gists", "/gists.{format}", - action="index", conditions=dict(method=["GET"])) + + m.connect("/gists/{gist_id}", action="update", conditions=dict(method=["PUT"])) m.connect("/gists/{gist_id}", action="delete", conditions=dict(method=["DELETE"])) m.connect("edit_gist", "/gists/{gist_id}/edit", - action="edit", conditions=dict(method=["GET"])) - m.connect("formatted_edit_gist", - "/gists/{gist_id}/{format}/edit", - action="edit", conditions=dict(method=["GET"])) + action="edit", conditions=dict(method=["GET", "POST"])) + m.connect("edit_gist_check_revision", "/gists/{gist_id}/edit/check_revision", + action="check_revision", conditions=dict(method=["POST"])) + + m.connect("gist", "/gists/{gist_id}", action="show", conditions=dict(method=["GET"])) - m.connect("formatted_gist", "/gists/{gist_id}/{format}", + m.connect("gist_rev", "/gists/{gist_id}/{revision}", + revision="tip", action="show", conditions=dict(method=["GET"])) - m.connect("formatted_gist_file", "/gists/{gist_id}/{format}/{revision}/{f_path:.*}", + m.connect("formatted_gist", "/gists/{gist_id}/{revision}/{format}", + revision="tip", + action="show", conditions=dict(method=["GET"])) + m.connect("formatted_gist_file", "/gists/{gist_id}/{revision}/{format}/{f_path:.*}", + revision='tip', action="show", conditions=dict(method=["GET"])) #ADMIN MAIN PAGES @@ -489,32 +532,109 @@ #========================================================================== # REPOSITORY ROUTES #========================================================================== + rmap.connect('repo_creating_home', '/{repo_name:.*?}/repo_creating', + controller='admin/repos', action='repo_creating') + rmap.connect('repo_check_home', '/{repo_name:.*?}/crepo_check', + controller='admin/repos', action='repo_check') + rmap.connect('summary_home', '/{repo_name:.*?}', controller='summary', conditions=dict(function=check_repo)) + # must be here for proper group/repo catching + rmap.connect('repos_group_home', '/{group_name:.*}', + controller='admin/repo_groups', action="show_by_name", + conditions=dict(function=check_group)) + rmap.connect('repo_stats_home', '/{repo_name:.*?}/statistics', + controller='summary', action='statistics', + conditions=dict(function=check_repo)) + rmap.connect('repo_size', '/{repo_name:.*?}/repo_size', controller='summary', action='repo_size', conditions=dict(function=check_repo)) - rmap.connect('repos_group_home', '/{group_name:.*}', - controller='admin/repos_groups', action="show_by_name", - conditions=dict(function=check_group)) + rmap.connect('branch_tag_switcher', '/{repo_name:.*?}/branches-tags', + controller='home', action='branch_tag_switcher') + rmap.connect('repo_refs_data', '/{repo_name:.*?}/refs-data', + controller='home', action='repo_refs_data') rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}', controller='changeset', revision='tip', conditions=dict(function=check_repo)) + rmap.connect('changeset_children', '/{repo_name:.*?}/changeset_children/{revision}', + controller='changeset', revision='tip', action="changeset_children", + conditions=dict(function=check_repo)) + rmap.connect('changeset_parents', '/{repo_name:.*?}/changeset_parents/{revision}', + controller='changeset', revision='tip', action="changeset_parents", + conditions=dict(function=check_repo)) - # no longer user, but kept for routes to work - rmap.connect("_edit_repo", "/{repo_name:.*?}/edit", - controller='admin/repos', action="edit", - conditions=dict(method=["GET"], function=check_repo) - ) - + # repo edit options rmap.connect("edit_repo", "/{repo_name:.*?}/settings", controller='admin/repos', action="edit", - conditions=dict(method=["GET"], function=check_repo) - ) + conditions=dict(method=["GET"], function=check_repo)) + + rmap.connect("edit_repo_perms", "/{repo_name:.*?}/settings/permissions", + controller='admin/repos', action="edit_permissions", + conditions=dict(method=["GET"], function=check_repo)) + rmap.connect("edit_repo_perms_update", "/{repo_name:.*?}/settings/permissions", + controller='admin/repos', action="edit_permissions_update", + conditions=dict(method=["PUT"], function=check_repo)) + rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions", + controller='admin/repos', action="edit_permissions_revoke", + conditions=dict(method=["DELETE"], function=check_repo)) + + rmap.connect("edit_repo_fields", "/{repo_name:.*?}/settings/fields", + controller='admin/repos', action="edit_fields", + conditions=dict(method=["GET"], function=check_repo)) + rmap.connect('create_repo_fields', "/{repo_name:.*?}/settings/fields/new", + controller='admin/repos', action="create_repo_field", + conditions=dict(method=["PUT"], function=check_repo)) + rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}", + controller='admin/repos', action="delete_repo_field", + conditions=dict(method=["DELETE"], function=check_repo)) + + + rmap.connect("edit_repo_advanced", "/{repo_name:.*?}/settings/advanced", + controller='admin/repos', action="edit_advanced", + conditions=dict(method=["GET"], function=check_repo)) + + rmap.connect("edit_repo_advanced_locking", "/{repo_name:.*?}/settings/advanced/locking", + controller='admin/repos', action="edit_advanced_locking", + conditions=dict(method=["PUT"], function=check_repo)) + rmap.connect('toggle_locking', "/{repo_name:.*?}/settings/advanced/locking_toggle", + controller='admin/repos', action="toggle_locking", + conditions=dict(method=["GET"], function=check_repo)) + + rmap.connect("edit_repo_advanced_journal", "/{repo_name:.*?}/settings/advanced/journal", + controller='admin/repos', action="edit_advanced_journal", + conditions=dict(method=["PUT"], function=check_repo)) + + rmap.connect("edit_repo_advanced_fork", "/{repo_name:.*?}/settings/advanced/fork", + controller='admin/repos', action="edit_advanced_fork", + conditions=dict(method=["PUT"], function=check_repo)) + + + rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches", + controller='admin/repos', action="edit_caches", + conditions=dict(method=["GET"], function=check_repo)) + rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches", + controller='admin/repos', action="edit_caches", + conditions=dict(method=["PUT"], function=check_repo)) + + + rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote", + controller='admin/repos', action="edit_remote", + conditions=dict(method=["GET"], function=check_repo)) + rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote", + controller='admin/repos', action="edit_remote", + conditions=dict(method=["PUT"], function=check_repo)) + + rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics", + controller='admin/repos', action="edit_statistics", + conditions=dict(method=["GET"], function=check_repo)) + rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics", + controller='admin/repos', action="edit_statistics", + conditions=dict(method=["PUT"], function=check_repo)) #still working url for backward compat. rmap.connect('raw_changeset_home_depraced', @@ -556,9 +676,14 @@ rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}', controller='changeset', action='changeset_info') + rmap.connect('compare_home', + '/{repo_name:.*?}/compare', + controller='compare', action='index', + conditions=dict(function=check_repo)) + rmap.connect('compare_url', '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}', - controller='compare', action='index', + controller='compare', action='compare', conditions=dict(function=check_repo), requirements=dict( org_ref_type='(branch|book|tag|rev|__other_ref_type__)', @@ -648,6 +773,11 @@ controller='files', action='history', revision='tip', f_path='', conditions=dict(function=check_repo)) + rmap.connect('files_authors_home', + '/{repo_name:.*?}/authors/{revision}/{f_path:.*}', + controller='files', action='authors', revision='tip', f_path='', + conditions=dict(function=check_repo)) + rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}', controller='files', action='diff', revision='tip', f_path='', conditions=dict(function=check_repo)) @@ -681,6 +811,11 @@ controller='files', action='add', revision='tip', f_path='', conditions=dict(function=check_repo)) + rmap.connect('files_delete_home', + '/{repo_name:.*?}/delete/{revision}/{f_path:.*}', + controller='files', action='delete', revision='tip', + f_path='', conditions=dict(function=check_repo)) + rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}', controller='files', action='archivefile', conditions=dict(function=check_repo)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/__init__.py --- a/rhodecode/controllers/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,13 @@ +# -*- 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 . diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/__init__.py --- a/rhodecode/controllers/admin/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,13 @@ +# -*- 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 . diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/admin.py --- a/rhodecode/controllers/admin/admin.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/admin.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.admin - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Controller for Admin panel of Rhodecode - - :created_on: Apr 7, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.admin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Controller for Admin panel of Rhodecode + +:created_on: Apr 7, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/auth_settings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/controllers/admin/auth_settings.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,139 @@ +# -*- 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 . +""" +rhodecode.controllers.admin.auth_settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +pluggable authentication controller for RhodeCode + +: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 rhodecode.lib import helpers as h +from rhodecode.lib.compat import json, formatted_json +from rhodecode.lib.base import BaseController, render +from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator +from rhodecode.lib import auth_modules +from rhodecode.model.forms import AuthSettingsForm +from rhodecode.model.db import RhodeCodeSetting +from rhodecode.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 = [ + 'rhodecode.lib.auth_modules.auth_rhodecode', + 'rhodecode.lib.auth_modules.auth_container', + 'rhodecode.lib.auth_modules.auth_ldap', + 'rhodecode.lib.auth_modules.auth_crowd', + ] + c.enabled_plugins = RhodeCodeSetting.get_auth_plugins() + + def index(self, defaults=None, errors=None, prefix_error=False): + self.__load_defaults() + _defaults = {} + # default plugins loaded + formglobals = { + "auth_plugins": ["rhodecode.lib.auth_modules.auth_rhodecode"] + } + formglobals.update(RhodeCodeSetting.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 = RhodeCodeSetting.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 = RhodeCodeSetting.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')) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/defaults.py --- a/rhodecode/controllers/admin/defaults.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/defaults.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.defaults - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - default settings controller for Rhodecode - - :created_on: Apr 27, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.defaults +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +default settings controller for Rhodecode + +:created_on: Apr 27, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import traceback @@ -89,8 +89,7 @@ try: form_result = _form.to_python(dict(request.POST)) for k, v in form_result.iteritems(): - setting = RhodeCodeSetting.get_by_name_or_create(k) - setting.app_settings_value = v + setting = RhodeCodeSetting.create_or_update(k, v) Session().add(setting) Session().commit() h.flash(_('Default settings updated successfully'), diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/gists.py --- a/rhodecode/controllers/admin/gists.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/gists.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.gist - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - gist controller for RhodeCode - - :created_on: May 9, 2013 - :author: marcink - :copyright: (C) 2010-2013 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.gist +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +gist controller for RhodeCode + +:created_on: May 9, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import time import logging import traceback @@ -35,15 +36,16 @@ from rhodecode.model.forms import GistForm from rhodecode.model.gist import GistModel from rhodecode.model.meta import Session -from rhodecode.model.db import Gist +from rhodecode.model.db import Gist, User from rhodecode.lib import helpers as h from rhodecode.lib.base import BaseController, render from rhodecode.lib.auth import LoginRequired, NotAnonymous +from rhodecode.lib.utils import jsonify from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime from rhodecode.lib.helpers import Page from webob.exc import HTTPNotFound, HTTPForbidden from sqlalchemy.sql.expression import or_ -from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError log = logging.getLogger(__name__) @@ -51,7 +53,7 @@ class GistsController(BaseController): """REST Controller styled on the Atom Publishing Protocol""" - def __load_defaults(self): + def __load_defaults(self, extra_values=None): c.lifetime_values = [ (str(-1), _('forever')), (str(5), _('5 minutes')), @@ -59,27 +61,42 @@ (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, format='html'): + def index(self): """GET /admin/gists: All items in the collection""" # url('gists') - c.show_private = request.GET.get('private') and c.rhodecode_user.username != 'default' - c.show_public = request.GET.get('public') and c.rhodecode_user.username != 'default' + not_default_user = c.rhodecode_user.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()) - if c.show_private: - c.gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\ + + # 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.rhodecode_user.user_id) - elif c.show_public: - c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\ + # 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.rhodecode_user.user_id) - else: - c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC) + # 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.rhodecode_user.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') @@ -94,7 +111,7 @@ try: form_result = gist_form.to_python(dict(request.POST)) #TODO: multiple files support, from the form - filename = form_result['filename'] or 'gistfile1.txt' + filename = form_result['filename'] or Gist.DEFAULT_FILENAME nodes = { filename: { 'content': form_result['content'], @@ -105,7 +122,7 @@ gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE gist = GistModel().create( description=form_result['description'], - owner=c.rhodecode_user, + owner=c.rhodecode_user.user_id, gist_mapping=nodes, gist_type=gist_type, lifetime=form_result['lifetime'] @@ -170,7 +187,7 @@ return redirect(url('gists')) @LoginRequired() - def show(self, gist_id, format='html', revision='tip', f_path=None): + 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) @@ -182,7 +199,8 @@ (time_to_datetime(c.gist.gist_expires))) raise HTTPNotFound() try: - c.file_changeset, c.files = GistModel().get_gist_files(gist_id) + c.file_changeset, c.files = GistModel().get_gist_files(gist_id, + revision=revision) except VCSError: log.error(traceback.format_exc()) raise HTTPNotFound() @@ -197,3 +215,78 @@ 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} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/ldap_settings.py --- a/rhodecode/controllers/admin/ldap_settings.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.ldap_settings - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - ldap controller for RhodeCode - - :created_on: Nov 26, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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 . -import logging -import formencode -import traceback - -from formencode import htmlfill - -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 rhodecode.lib.base import BaseController, render -from rhodecode.lib import helpers as h -from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator -from rhodecode.lib.exceptions import LdapImportError -from rhodecode.model.forms import LdapSettingsForm -from rhodecode.model.db import RhodeCodeSetting -from rhodecode.model.meta import Session - -log = logging.getLogger(__name__) - - -class LdapSettingsController(BaseController): - - search_scope_choices = [('BASE', _('BASE'),), - ('ONELEVEL', _('ONELEVEL'),), - ('SUBTREE', _('SUBTREE'),), - ] - search_scope_default = 'SUBTREE' - - tls_reqcert_choices = [('NEVER', _('NEVER'),), - ('ALLOW', _('ALLOW'),), - ('TRY', _('TRY'),), - ('DEMAND', _('DEMAND'),), - ('HARD', _('HARD'),), - ] - tls_reqcert_default = 'DEMAND' - - tls_kind_choices = [('PLAIN', _('No encryption'),), - ('LDAPS', _('LDAPS connection'),), - ('START_TLS', _('START_TLS on LDAP connection'),) - ] - - tls_kind_default = 'PLAIN' - - @LoginRequired() - @HasPermissionAllDecorator('hg.admin') - def __before__(self): - c.search_scope_choices = self.search_scope_choices - c.tls_reqcert_choices = self.tls_reqcert_choices - c.tls_kind_choices = self.tls_kind_choices - - c.search_scope_cur = self.search_scope_default - c.tls_reqcert_cur = self.tls_reqcert_default - c.tls_kind_cur = self.tls_kind_default - - super(LdapSettingsController, self).__before__() - - def index(self): - defaults = RhodeCodeSetting.get_ldap_settings() - c.search_scope_cur = defaults.get('ldap_search_scope') - c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert') - c.tls_kind_cur = defaults.get('ldap_tls_kind') - - return htmlfill.render( - render('admin/ldap/ldap.html'), - defaults=defaults, - encoding="UTF-8", - force_defaults=True,) - - def ldap_settings(self): - """POST ldap create and store ldap settings""" - - _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices], - [x[0] for x in self.search_scope_choices], - [x[0] for x in self.tls_kind_choices])() - # check the ldap lib - ldap_active = False - try: - import ldap - ldap_active = True - except ImportError: - pass - - try: - form_result = _form.to_python(dict(request.POST)) - - try: - - for k, v in form_result.items(): - if k.startswith('ldap_'): - if k == 'ldap_active': - v = v if ldap_active else False - setting = RhodeCodeSetting.get_by_name(k) - setting.app_settings_value = v - Session().add(setting) - - Session().commit() - h.flash(_('LDAP settings updated successfully'), - category='success') - if not ldap_active: - #if ldap is missing send an info to user - h.flash(_('Unable to activate ldap. The "python-ldap" ' - 'library is missing.'), category='warning') - - except (DatabaseError,): - raise - - except formencode.Invalid, errors: - e = errors.error_dict or {} - - return htmlfill.render( - render('admin/ldap/ldap.html'), - defaults=errors.value, - errors=e, - prefix_error=False, - encoding="UTF-8") - except Exception: - log.error(traceback.format_exc()) - h.flash(_('Error occurred during update of ldap settings'), - category='error') - - return redirect(url('ldap_home')) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/my_account.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/controllers/admin/my_account.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,290 @@ +# -*- 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 . +""" +rhodecode.controllers.admin.my_account +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +my account controller for rhodecode admin + +:created_on: August 20, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE 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 rhodecode.lib import helpers as h +from rhodecode.lib.auth import LoginRequired, NotAnonymous, AuthUser +from rhodecode.lib.base import BaseController, render +from rhodecode.lib.utils2 import generate_api_key, safe_int +from rhodecode.lib.compat import json +from rhodecode.model.db import Repository, PullRequest, PullRequestReviewers, \ + UserEmailMap, UserApiKeys, User, UserFollowing +from rhodecode.model.forms import UserForm, PasswordChangeForm +from rhodecode.model.user import UserModel +from rhodecode.model.repo import RepoModel +from rhodecode.model.api_key import ApiKeyModel +from rhodecode.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.rhodecode_user.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.rhodecode_user.user_id).all()] + else: + admin = True + repos_list = Session().query(Repository)\ + .filter(Repository.user_id == + self.rhodecode_user.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.rhodecode_user.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.rhodecode_user.user_id, + 'email': self.rhodecode_user.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 != "rhodecode": + # forbid updating username for external accounts + skip_attrs.append('username') + + UserModel().update(self.rhodecode_user.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.rhodecode_user.username)() + try: + form_result = _form.to_python(request.POST) + UserModel().update(self.rhodecode_user.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.rhodecode_user.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.rhodecode_user.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.rhodecode_user.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.rhodecode_user.user_id)\ + .all()) + my_prs = [x.pull_request for x in PullRequestReviewers.query() + .filter(PullRequestReviewers.user_id == + self.rhodecode_user.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.rhodecode_user.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.rhodecode_user.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.rhodecode_user.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.rhodecode_user.user_id) + Session().commit() + h.flash(_("Api key successfully deleted"), category='success') + + return redirect(url('my_account_api_keys')) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/notifications.py --- a/rhodecode/controllers/admin/notifications.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/notifications.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.notifications - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - notifications controller for RhodeCode - - :created_on: Nov 23, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.notifications +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +notifications controller for RhodeCode + +:created_on: Nov 23, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import traceback @@ -29,6 +29,7 @@ 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 rhodecode.model.db import Notification from rhodecode.model.notification import NotificationModel @@ -111,13 +112,14 @@ owner = all(un.user.user_id == c.rhodecode_user.user_id for un in no.notifications_to_users) if h.HasPermissionAny('hg.admin')() or owner: - NotificationModel().mark_read(c.rhodecode_user.user_id, no) - Session().commit() - return 'ok' + # deletes only notification2user + NotificationModel().mark_read(c.rhodecode_user.user_id, no) + Session().commit() + return 'ok' except Exception: Session().rollback() log.error(traceback.format_exc()) - return 'fail' + raise HTTPBadRequest() def delete(self, notification_id): """DELETE /_admin/notifications/id: Delete an existing item""" @@ -127,19 +129,19 @@ # h.form(url('notification', notification_id=ID), # method='delete') # url('notification', notification_id=ID) - try: no = Notification.get(notification_id) - owner = all(un.user.user_id == c.rhodecode_user.user_id + owner = any(un.user.user_id == c.rhodecode_user.user_id for un in no.notifications_to_users) if h.HasPermissionAny('hg.admin')() or owner: - NotificationModel().delete(c.rhodecode_user.user_id, no) - Session().commit() - return 'ok' + # deletes only notification2user + NotificationModel().delete(c.rhodecode_user.user_id, no) + Session().commit() + return 'ok' except Exception: Session().rollback() log.error(traceback.format_exc()) - return 'fail' + raise HTTPBadRequest() def show(self, notification_id, format='html'): """GET /_admin/notifications/id: Show a specific item""" @@ -149,8 +151,8 @@ owner = any(un.user.user_id == c.rhodecode_user.user_id for un in no.notifications_to_users) - - if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner): + 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) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/permissions.py --- a/rhodecode/controllers/admin/permissions.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/permissions.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.permissions - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - permissions controller for Rhodecode - - :created_on: Apr 27, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.permissions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +permissions controller for Rhodecode + +:created_on: Apr 27, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback @@ -55,6 +56,7 @@ def __before__(self): super(PermissionsController, self).__before__() + def __load_data(self): c.repo_perms_choices = [('repository.none', _('None'),), ('repository.read', _('Read'),), ('repository.write', _('Write'),), @@ -83,6 +85,11 @@ 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'))] @@ -92,50 +99,28 @@ c.fork_choices = [('hg.fork.none', _('Disabled')), ('hg.fork.repository', _('Enabled'))] - def index(self, format='html'): - """GET /permissions: All items in the collection""" - # url('permissions') - - def create(self): - """POST /permissions: Create a new item""" - # url('permissions') - - def new(self, format='html'): - """GET /permissions/new: Form to create a new item""" - # url('new_permission') - - def update(self, id): - """PUT /permissions/id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('permission', id=ID), - # method='put') - # url('permission', id=ID) - if id == 'default': - c.user = default_user = User.get_default_user() - c.perm_user = AuthUser(user_id=default_user.user_id) - c.user_ip_map = UserIpMap.query()\ - .filter(UserIpMap.user == default_user).all() - + 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_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], - )() + [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': id}) + form_result.update({'perm_user_name': 'default'}) PermissionModel().update(form_result) Session().commit() - h.flash(_('Default permissions updated successfully'), + h.flash(_('Global permissions updated successfully'), category='success') except formencode.Invalid, errors: @@ -152,66 +137,58 @@ h.flash(_('Error occurred during update of permissions'), category='error') - return redirect(url('edit_permission', id=id)) + return redirect(url('admin_permissions')) + + c.user = User.get_default_user() + defaults = {'anonymous': c.user.active} - def delete(self, id): - """DELETE /permissions/id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('permission', id=ID), - # method='delete') - # url('permission', id=ID) + 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 - def show(self, id, format='html'): - """GET /permissions/id: Show a specific item""" - # url('permission', id=ID) - Permission.get_or_404(-1) + if p.permission.permission_name.startswith('usergroup.'): + defaults['default_user_group_perm'] = p.permission.permission_name - def edit(self, id, format='html'): - """GET /permissions/id/edit: Form to edit an existing item""" - #url('edit_permission', id=ID) + if p.permission.permission_name.startswith('hg.create.write_on_repogroup'): + defaults['create_on_write'] = p.permission.permission_name - #this form can only edit default user permissions - if id == 'default': - c.user = User.get_default_user() - defaults = {'anonymous': c.user.active} - c.perm_user = c.user.AuthUser - c.user_ip_map = UserIpMap.query()\ - .filter(UserIpMap.user == c.user).all() - for p in c.user.user_perms: - if p.permission.permission_name.startswith('repository.'): - defaults['default_repo_perm'] = 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('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.register.'): + defaults['default_register'] = p.permission.permission_name - if p.permission.permission_name.startswith('hg.create.'): - defaults['default_repo_create'] = 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.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.fork.'): + defaults['default_fork'] = 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) - return htmlfill.render( - render('admin/permissions/permissions.html'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False - ) - else: - return redirect(url('admin_home')) + 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') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/repo_groups.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/controllers/admin/repo_groups.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,471 @@ +# -*- 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 . +""" +rhodecode.controllers.admin.repo_groups +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Repository groups controller for RhodeCode + +:created_on: Mar 23, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE 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 rhodecode +from rhodecode.lib import helpers as h +from rhodecode.lib.compat import json +from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\ + HasRepoGroupPermissionAnyDecorator, HasRepoGroupPermissionAll,\ + HasPermissionAll +from rhodecode.lib.base import BaseController, render +from rhodecode.model.db import RepoGroup, Repository +from rhodecode.model.scm import RepoGroupList +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm +from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel +from webob.exc import HTTPInternalServerError, HTTPNotFound +from rhodecode.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.rhodecode_user.username == u[0], + form_result['perms_updates']) + _new = filter(lambda u: c.rhodecode_user.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 = rhodecode.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.rhodecode_user.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: + # + # 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: + # + # 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): + """ + Update permissions for given repository group + + :param group_name: + """ + + c.repo_group = RepoGroupModel()._get_repo_group(group_name) + valid_recursive_choices = ['none', 'repos', 'groups', 'all'] + form_result = RepoGroupPermsForm(valid_recursive_choices)().to_python(request.POST) + if not c.rhodecode_user.is_admin: + if self._revoke_perms_on_yourself(form_result): + msg = _('Cannot revoke permission for yourself as admin') + h.flash(msg, category='warning') + return redirect(url('edit_repo_group_perms', group_name=group_name)) + recursive = form_result['recursive'] + # iterate over all members(if in recursive mode) of this groups and + # set the permissions ! + # this can be potentially heavy operation + RepoGroupModel()._update_permissions(c.repo_group, + form_result['perms_new'], + form_result['perms_updates'], + recursive) + #TODO: implement this + #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions', + # repo_name, self.ip_addr, self.sa) + Session().commit() + h.flash(_('Repository Group permissions updated'), category='success') + return redirect(url('edit_repo_group_perms', group_name=group_name)) + + @HasRepoGroupPermissionAnyDecorator('group.admin') + def delete_perms(self, group_name): + """ + 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.rhodecode_user.is_admin: + if obj_type == 'user' and c.rhodecode_user.user_id == obj_id: + msg = _('Cannot revoke permission for yourself as admin') + h.flash(msg, category='warning') + raise Exception('revoke admin permission on self') + recursive = request.POST.get('recursive', 'none') + if obj_type == 'user': + RepoGroupModel().delete_permission(repo_group=group_name, + obj=obj_id, obj_type='user', + recursive=recursive) + elif obj_type == 'user_group': + RepoGroupModel().delete_permission(repo_group=group_name, + obj=obj_id, + obj_type='user_group', + recursive=recursive) + + Session().commit() + except Exception: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during revoking of permission'), + category='error') + raise HTTPInternalServerError() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/repos.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.repos - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Repositories controller for RhodeCode - - :created_on: Apr 7, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,26 +11,36 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.repos +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Repositories controller for RhodeCode + +:created_on: Apr 7, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import traceback import formencode from formencode import htmlfill - -from webob.exc import HTTPInternalServerError, HTTPForbidden -from pylons import request, session, tmpl_context as c, url +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.exc import IntegrityError +from sqlalchemy.sql.expression import func -import rhodecode from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ - HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\ - HasPermissionAny, HasReposGroupPermissionAny, HasRepoPermissionAnyDecorator + HasRepoPermissionAllDecorator, NotAnonymous,HasPermissionAny, \ + HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render -from rhodecode.lib.utils import action_logger, repo_name_slug +from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify from rhodecode.lib.helpers import get_token +from rhodecode.lib.vcs import RepositoryError from rhodecode.model.meta import Session from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\ RhodeCodeSetting, RepositoryField @@ -49,7 +48,6 @@ from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList from rhodecode.model.repo import RepoModel from rhodecode.lib.compat import json -from sqlalchemy.sql.expression import func from rhodecode.lib.exceptions import AttachedForksError from rhodecode.lib.utils2 import safe_int @@ -67,15 +65,32 @@ def __before__(self): super(ReposController, self).__before__() - def __load_defaults(self): + 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) - repo_model = RepoModel() - c.users_array = repo_model.get_users_js() - c.users_groups_array = repo_model.get_users_groups_js() + # 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 @@ -85,62 +100,24 @@ :param repo_name: """ - self.__load_defaults() - - c.repo_info = db_repo = Repository.get_by_repo_name(repo_name) - repo = db_repo.scm_instance - - if c.repo_info is None: - h.not_mapped_error(repo_name) - return redirect(url('repos')) + 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 - - c.default_user_id = User.get_default_user().user_id - c.in_public_journal = UserFollowing.query()\ - .filter(UserFollowing.user_id == c.default_user_id)\ - .filter(UserFollowing.follows_repository == c.repo_info).scalar() - - if c.repo_info.stats: - # this is on what revision we ended up so we add +1 for count - last_rev = c.repo_info.stats.stat_on_revision + 1 - else: - last_rev = 0 - c.stats_revision = last_rev - - c.repo_last_rev = repo.count() if repo.revisions else 0 - - if last_rev == 0 or c.repo_last_rev == 0: - c.stats_percentage = 0 - else: - c.stats_percentage = '%.2f' % ((float((last_rev)) / - c.repo_last_rev) * 100) - - c.repo_fields = RepositoryField.query()\ - .filter(RepositoryField.repository == db_repo).all() - defaults = RepoModel()._get_defaults(repo_name) - _repos = Repository.query().order_by(Repository.repo_name).all() - read_access_repos = RepoList(_repos) - c.repos_list = [('', _('--REMOVE FORK--'))] - c.repos_list += [(x.repo_id, x.repo_name) - for x in read_access_repos - if x.repo_id != c.repo_info.repo_id] - - defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else '' return defaults def index(self, format='html'): """GET /repos: All items in the collection""" # url('repos') - repo_list = Repository.query()\ - .order_by(func.lower(Repository.repo_name))\ - .all() + _list = Repository.query()\ + .order_by(func.lower(Repository.repo_name))\ + .all() - c.repos_list = RepoList(repo_list, perm_set=['repository.admin']) + 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) @@ -157,33 +134,19 @@ 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)) - new_repo = RepoModel().create(form_result, - self.rhodecode_user.user_id) - if form_result['clone_uri']: - h.flash(_('Created repository %s from %s') \ - % (form_result['repo_name'], form_result['clone_uri']), - category='success') - else: - repo_url = h.link_to(form_result['repo_name'], - h.url('summary_home', repo_name=form_result['repo_name_full'])) - h.flash(h.literal(_('Created repository %s') % repo_url), - category='success') - - if request.POST.get('user_created'): - # created by regular non admin user - action_logger(self.rhodecode_user, 'user_created_repo', - form_result['repo_name_full'], self.ip_addr, - self.sa) - else: - action_logger(self.rhodecode_user, 'admin_created_repo', - form_result['repo_name_full'], self.ip_addr, - self.sa) - Session().commit() + # create is done sometimes async on celery, db transaction + # management is handled there. + task = RepoModel().create(form_result, self.rhodecode_user.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'), @@ -194,14 +157,14 @@ except Exception: log.error(traceback.format_exc()) - msg = _('Error creating repository %s') \ - % form_result.get('repo_name') + msg = (_('Error creating repository %s') + % form_result.get('repo_name')) h.flash(msg, category='error') - if c.rhodecode_user.is_admin: - return redirect(url('repos')) return redirect(url('home')) - #redirect to our new repo ! - return redirect(url('summary_home', repo_name=new_repo.repo_name)) + + return redirect(h.url('repo_creating_home', + repo_name=form_result['repo_name_full'], + task_id=task_id)) @NotAnonymous() def create_repository(self): @@ -213,7 +176,11 @@ #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 - if not HasReposGroupPermissionAny('group.admin', 'group.write')(group_name=gr_name): + # 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(), @@ -237,6 +204,51 @@ 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 rhodecode 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): """ @@ -247,7 +259,12 @@ # h.form(url('repo', repo_name=ID), # method='put') # url('repo', repo_name=ID) - self.__load_defaults() + 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 ! @@ -333,8 +350,46 @@ 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 set_repo_perm_member(self, repo_name): + 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']) @@ -343,15 +398,9 @@ # repo_name, self.ip_addr, self.sa) Session().commit() h.flash(_('Repository permissions updated'), category='success') - return redirect(url('edit_repo', repo_name=repo_name)) + return redirect(url('edit_repo_perms', repo_name=repo_name)) - @HasRepoPermissionAllDecorator('repository.admin') - def delete_repo_perm_member(self, repo_name): - """ - DELETE an existing repository permission user - - :param repo_name: - """ + def edit_permissions_revoke(self, repo_name): try: obj_type = request.POST.get('obj_type') obj_id = None @@ -363,7 +412,7 @@ if obj_type == 'user': RepoModel().revoke_user_permission(repo=repo_name, user=obj_id) elif obj_type == 'user_group': - RepoModel().revoke_users_group_permission( + RepoModel().revoke_user_group_permission( repo=repo_name, group_name=obj_id ) #TODO: implement this @@ -377,58 +426,155 @@ raise HTTPInternalServerError() @HasRepoPermissionAllDecorator('repository.admin') - def repo_stats(self, repo_name): + def edit_fields(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) + c.repo_fields = RepositoryField.query()\ + .filter(RepositoryField.repository == c.repo_info).all() + c.active = 'fields' + if request.POST: + + return redirect(url('repo_edit_fields')) + return render('admin/repos/repo_edit.html') + + @HasRepoPermissionAllDecorator('repository.admin') + def create_repo_field(self, repo_name): + try: + form_result = RepoFieldForm()().to_python(dict(request.POST)) + new_field = RepositoryField() + new_field.repository = Repository.get_by_repo_name(repo_name) + new_field.field_key = form_result['new_field_key'] + new_field.field_type = form_result['new_field_type'] # python type + new_field.field_value = form_result['new_field_value'] # set initial blank value + new_field.field_desc = form_result['new_field_desc'] + new_field.field_label = form_result['new_field_label'] + Session().add(new_field) + Session().commit() + except Exception, e: + log.error(traceback.format_exc()) + msg = _('An error occurred during creation of field') + if isinstance(e, formencode.Invalid): + msg += ". " + e.msg + h.flash(msg, category='error') + return redirect(url('edit_repo_fields', repo_name=repo_name)) + + @HasRepoPermissionAllDecorator('repository.admin') + def delete_repo_field(self, repo_name, field_id): + field = RepositoryField.get_or_404(field_id) + try: + Session().delete(field) + Session().commit() + except Exception, e: + log.error(traceback.format_exc()) + msg = _('An error occurred during removal of field') + h.flash(msg, category='error') + return redirect(url('edit_repo_fields', repo_name=repo_name)) + + @HasRepoPermissionAllDecorator('repository.admin') + def edit_advanced(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) + c.default_user_id = User.get_default_user().user_id + c.in_public_journal = UserFollowing.query()\ + .filter(UserFollowing.user_id == c.default_user_id)\ + .filter(UserFollowing.follows_repository == c.repo_info).scalar() + + _repos = Repository.query().order_by(Repository.repo_name).all() + read_access_repos = RepoList(_repos) + c.repos_list = [(None, _('-- Not a fork --'))] + c.repos_list += [(x.repo_id, x.repo_name) + for x in read_access_repos + if x.repo_id != c.repo_info.repo_id] + + defaults = { + 'id_fork_of': c.repo_info.fork.repo_id if c.repo_info.fork else '' + } + + c.active = 'advanced' + if request.POST: + return redirect(url('repo_edit_advanced')) + return htmlfill.render( + render('admin/repos/repo_edit.html'), + defaults=defaults, + encoding="UTF-8", + force_defaults=False) + + @HasRepoPermissionAllDecorator('repository.admin') + def edit_advanced_journal(self, repo_name): """ - DELETE an existing repository statistics + Set's this repository to be visible in public journal, + in other words assing default user to follow this repo :param repo_name: """ - try: - RepoModel().delete_stats(repo_name) - Session().commit() - except Exception, e: - log.error(traceback.format_exc()) - h.flash(_('An error occurred during deletion of repository stats'), - category='error') - return redirect(url('edit_repo', repo_name=repo_name)) + cur_token = request.POST.get('auth_token') + token = get_token() + if cur_token == token: + try: + repo_id = Repository.get_by_repo_name(repo_name).repo_id + user_id = User.get_default_user().user_id + self.scm_model.toggle_following_repo(repo_id, user_id) + h.flash(_('Updated repository visibility in public journal'), + category='success') + Session().commit() + except Exception: + h.flash(_('An error occurred during setting this' + ' repository in public journal'), + category='error') + + else: + h.flash(_('Token mismatch'), category='error') + return redirect(url('edit_repo_advanced', repo_name=repo_name)) + @HasRepoPermissionAllDecorator('repository.admin') - def repo_cache(self, repo_name): + def edit_advanced_fork(self, repo_name): """ - INVALIDATE existing repository cache + Mark given repository as a fork of another :param repo_name: """ - try: - ScmModel().mark_for_invalidation(repo_name) + fork_id = request.POST.get('id_fork_of') + repo = ScmModel().mark_as_fork(repo_name, fork_id, + self.rhodecode_user.username) + fork = repo.fork.repo_name if repo.fork else _('Nothing') Session().commit() + h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork), + category='success') + except RepositoryError, e: + log.error(traceback.format_exc()) + h.flash(str(e), category='error') except Exception, e: log.error(traceback.format_exc()) - h.flash(_('An error occurred during cache invalidation'), + h.flash(_('An error occurred during this operation'), category='error') - return redirect(url('edit_repo', repo_name=repo_name)) + + return redirect(url('edit_repo_advanced', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') - def repo_locking(self, repo_name): + def edit_advanced_locking(self, repo_name): """ Unlock repository when it is locked ! :param repo_name: """ - try: repo = Repository.get_by_repo_name(repo_name) if request.POST.get('set_lock'): Repository.lock(repo, c.rhodecode_user.user_id) + h.flash(_('Locked repository'), category='success') elif request.POST.get('set_unlock'): Repository.unlock(repo) + h.flash(_('Unlocked repository'), category='success') except Exception, e: log.error(traceback.format_exc()) h.flash(_('An error occurred during unlocking'), category='error') - return redirect(url('edit_repo', repo_name=repo_name)) + return redirect(url('edit_repo_advanced', repo_name=repo_name)) @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') def toggle_locking(self, repo_name): @@ -458,121 +604,72 @@ return redirect(url('summary_home', repo_name=repo_name)) @HasRepoPermissionAllDecorator('repository.admin') - def repo_public_journal(self, repo_name): - """ - Set's this repository to be visible in public journal, - in other words assing default user to follow this repo - - :param repo_name: - """ - - cur_token = request.POST.get('auth_token') - token = get_token() - if cur_token == token: + def edit_caches(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) + c.active = 'caches' + if request.POST: try: - repo_id = Repository.get_by_repo_name(repo_name).repo_id - user_id = User.get_default_user().user_id - self.scm_model.toggle_following_repo(repo_id, user_id) - h.flash(_('Updated repository visibility in public journal'), + ScmModel().mark_for_invalidation(repo_name, delete=True) + Session().commit() + h.flash(_('Cache invalidation successful'), category='success') - Session().commit() - except Exception: - h.flash(_('An error occurred during setting this' - ' repository in public journal'), + except Exception, e: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during cache invalidation'), category='error') - else: - h.flash(_('Token mismatch'), category='error') - return redirect(url('edit_repo', repo_name=repo_name)) - - @HasRepoPermissionAllDecorator('repository.admin') - def repo_pull(self, repo_name): - """ - Runs task to update given repository with remote changes, - ie. make pull on remote location - - :param repo_name: - """ - try: - ScmModel().pull_changes(repo_name, self.rhodecode_user.username) - h.flash(_('Pulled from remote location'), category='success') - except Exception, e: - log.error(traceback.format_exc()) - h.flash(_('An error occurred during pull from remote location'), - category='error') - - return redirect(url('edit_repo', repo_name=repo_name)) + return redirect(url('edit_repo_caches', repo_name=c.repo_name)) + return render('admin/repos/repo_edit.html') @HasRepoPermissionAllDecorator('repository.admin') - def repo_as_fork(self, repo_name): - """ - Mark given repository as a fork of another - - :param repo_name: - """ - try: - fork_id = request.POST.get('id_fork_of') - repo = ScmModel().mark_as_fork(repo_name, fork_id, - self.rhodecode_user.username) - fork = repo.fork.repo_name if repo.fork else _('Nothing') - Session().commit() - h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork), - category='success') - except Exception, e: - log.error(traceback.format_exc()) - h.flash(_('An error occurred during this operation'), - category='error') - - return redirect(url('edit_repo', repo_name=repo_name)) - - @HasPermissionAllDecorator('hg.admin') - def show(self, repo_name, format='html'): - """GET /repos/repo_name: Show a specific item""" - # url('repo', repo_name=ID) + def edit_remote(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) + c.active = 'remote' + if request.POST: + try: + ScmModel().pull_changes(repo_name, self.rhodecode_user.username) + h.flash(_('Pulled from remote location'), category='success') + except Exception, e: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during pull from remote location'), + category='error') + return redirect(url('edit_repo_remote', repo_name=c.repo_name)) + return render('admin/repos/repo_edit.html') @HasRepoPermissionAllDecorator('repository.admin') - def edit(self, repo_name, format='html'): - """GET /repos/repo_name/edit: Form to edit an existing item""" + def edit_statistics(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) + c.repo_info = self._load_repo(repo_name) + repo = c.repo_info.scm_instance - return htmlfill.render( - render('admin/repos/repo_edit.html'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False - ) + if c.repo_info.stats: + # this is on what revision we ended up so we add +1 for count + last_rev = c.repo_info.stats.stat_on_revision + 1 + else: + last_rev = 0 + c.stats_revision = last_rev + + c.repo_last_rev = repo.count() if repo.revisions else 0 - @HasPermissionAllDecorator('hg.admin') - def create_repo_field(self, repo_name): - try: - form_result = RepoFieldForm()().to_python(dict(request.POST)) - new_field = RepositoryField() - new_field.repository = Repository.get_by_repo_name(repo_name) - new_field.field_key = form_result['new_field_key'] - new_field.field_type = form_result['new_field_type'] # python type - new_field.field_value = form_result['new_field_value'] # set initial blank value - new_field.field_desc = form_result['new_field_desc'] - new_field.field_label = form_result['new_field_label'] - Session().add(new_field) - Session().commit() + if last_rev == 0 or c.repo_last_rev == 0: + c.stats_percentage = 0 + else: + c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100) - except Exception, e: - log.error(traceback.format_exc()) - msg = _('An error occurred during creation of field') - if isinstance(e, formencode.Invalid): - msg += ". " + e.msg - h.flash(msg, category='error') - return redirect(url('edit_repo', repo_name=repo_name)) + c.active = 'statistics' + if request.POST: + try: + RepoModel().delete_stats(repo_name) + Session().commit() + except Exception, e: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during deletion of repository stats'), + category='error') + return redirect(url('edit_repo_statistics', repo_name=c.repo_name)) - @HasPermissionAllDecorator('hg.admin') - def delete_repo_field(self, repo_name, field_id): - field = RepositoryField.get_or_404(field_id) - try: - Session().delete(field) - Session().commit() - except Exception, e: - log.error(traceback.format_exc()) - msg = _('An error occurred during removal of field') - h.flash(msg, category='error') - return redirect(url('edit_repo', repo_name=repo_name)) + return render('admin/repos/repo_edit.html') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/repos_groups.py --- a/rhodecode/controllers/admin/repos_groups.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,401 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.repos_groups - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Repository groups controller for RhodeCode - - :created_on: Mar 23, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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 . - -import logging -import traceback -import formencode - -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 _ - -from sqlalchemy.exc import IntegrityError - -import rhodecode -from rhodecode.lib import helpers as h -from rhodecode.lib.compat import json -from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\ - HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\ - HasPermissionAll -from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import RepoGroup, Repository -from rhodecode.model.scm import RepoGroupList -from rhodecode.model.repos_group import ReposGroupModel -from rhodecode.model.forms import ReposGroupForm, RepoGroupPermsForm -from rhodecode.model.meta import Session -from rhodecode.model.repo import RepoModel -from webob.exc import HTTPInternalServerError, HTTPNotFound -from rhodecode.lib.utils2 import str2bool, safe_int -from sqlalchemy.sql.expression import func - - -log = logging.getLogger(__name__) - - -class ReposGroupsController(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('repos_group', 'repos_groups') - - @LoginRequired() - def __before__(self): - super(ReposGroupsController, 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.users_groups_array = repo_model.get_users_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.rhodecode_user.username == u[0], - form_result['perms_updates']) - _new = filter(lambda u: c.rhodecode_user.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 /repos_groups: All items in the collection""" - # url('repos_groups') - group_iter = RepoGroupList(RepoGroup.query().all(), - perm_set=['group.admin']) - sk = lambda g: g.parents[0].group_name if g.parents else g.group_name - c.groups = sorted(group_iter, key=sk) - return render('admin/repos_groups/repos_groups_show.html') - - def create(self): - """POST /repos_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 - repos_group_form = ReposGroupForm(available_groups= - map(lambda k: unicode(k[0]), c.repo_groups))() - try: - form_result = repos_group_form.to_python(dict(request.POST)) - ReposGroupModel().create( - group_name=form_result['group_name'], - group_description=form_result['group_description'], - parent=form_result['group_parent_id'], - owner=self.rhodecode_user.user_id - ) - 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/repos_groups/repos_groups_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, format='html'): - """GET /repos_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 HasReposGroupPermissionAll('group.admin')(group_name, 'group create'): - pass - else: - return abort(403) - - self.__load_defaults() - return render('admin/repos_groups/repos_groups_add.html') - - @HasReposGroupPermissionAnyDecorator('group.admin') - def update(self, group_name): - """PUT /repos_groups/group_name: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('repos_group', group_name=GROUP_NAME), - # method='put') - # url('repos_group', group_name=GROUP_NAME) - - c.repos_group = ReposGroupModel()._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.repos_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.repos_group.group_id]) - - repos_group_form = ReposGroupForm( - edit=True, - old_data=c.repos_group.get_dict(), - available_groups=c.repo_groups_choices, - can_create_in_root=allow_empty_group, - )() - try: - form_result = repos_group_form.to_python(dict(request.POST)) - - new_gr = ReposGroupModel().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/repos_groups/repos_groups_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_repos_group', group_name=group_name)) - - @HasReposGroupPermissionAnyDecorator('group.admin') - def delete(self, group_name): - """DELETE /repos_groups/group_name: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('repos_group', group_name=GROUP_NAME), - # method='delete') - # url('repos_group', group_name=GROUP_NAME) - - gr = c.repos_group = ReposGroupModel()._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: - ReposGroupModel().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')) - - @HasReposGroupPermissionAnyDecorator('group.admin') - def set_repo_group_perm_member(self, group_name): - c.repos_group = ReposGroupModel()._get_repo_group(group_name) - form_result = RepoGroupPermsForm()().to_python(request.POST) - if not c.rhodecode_user.is_admin: - if self._revoke_perms_on_yourself(form_result): - msg = _('Cannot revoke permission for yourself as admin') - h.flash(msg, category='warning') - return redirect(url('edit_repos_group', group_name=group_name)) - recursive = form_result['recursive'] - # iterate over all members(if in recursive mode) of this groups and - # set the permissions ! - # this can be potentially heavy operation - ReposGroupModel()._update_permissions(c.repos_group, - form_result['perms_new'], - form_result['perms_updates'], - recursive) - #TODO: implement this - #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions', - # repo_name, self.ip_addr, self.sa) - Session().commit() - h.flash(_('Repository Group permissions updated'), category='success') - return redirect(url('edit_repos_group', group_name=group_name)) - - @HasReposGroupPermissionAnyDecorator('group.admin') - def delete_repo_group_perm_member(self, group_name): - """ - 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.rhodecode_user.is_admin: - if obj_type == 'user' and c.rhodecode_user.user_id == obj_id: - msg = _('Cannot revoke permission for yourself as admin') - h.flash(msg, category='warning') - raise Exception('revoke admin permission on self') - recursive = str2bool(request.POST.get('recursive', False)) - if obj_type == 'user': - ReposGroupModel().delete_permission( - repos_group=group_name, obj=obj_id, - obj_type='user', recursive=recursive - ) - elif obj_type == 'user_group': - ReposGroupModel().delete_permission( - repos_group=group_name, obj=obj_id, - obj_type='users_group', recursive=recursive - ) - - Session().commit() - except Exception: - log.error(traceback.format_exc()) - h.flash(_('An error occurred during revoking of permission'), - category='error') - raise HTTPInternalServerError() - - 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(id_.group_id) - raise HTTPNotFound - - @HasReposGroupPermissionAnyDecorator('group.read', 'group.write', - 'group.admin') - def show(self, group_name, format='html'): - """GET /repos_groups/group_name: Show a specific item""" - # url('repos_group', group_name=GROUP_NAME) - - c.group = c.repos_group = ReposGroupModel()._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_repos_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/repos_groups/repos_groups.html') - - @HasReposGroupPermissionAnyDecorator('group.admin') - def edit(self, group_name, format='html'): - """GET /repos_groups/group_name/edit: Form to edit an existing item""" - # url('edit_repos_group', group_name=GROUP_NAME) - - c.repos_group = ReposGroupModel()._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.repos_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.repos_group.group_id]) - defaults = self.__load_data(c.repos_group.group_id) - - return htmlfill.render( - render('admin/repos_groups/repos_groups_edit.html'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False - ) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/settings.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.settings - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - settings controller for rhodecode admin - - :created_on: Jul 14, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,39 +11,42 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +settings controller for rhodecode admin + +:created_on: Jul 14, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import time import logging import traceback import formencode -import pkg_resources -import platform -from sqlalchemy import func from formencode import htmlfill -from pylons import request, session, tmpl_context as c, url, config -from pylons.controllers.util import abort, redirect +from pylons import request, tmpl_context as c, url, config +from pylons.controllers.util import redirect from pylons.i18n.translation import _ from rhodecode.lib import helpers as h -from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ - HasPermissionAnyDecorator, NotAnonymous, HasPermissionAny,\ - HasReposGroupPermissionAll, HasReposGroupPermissionAny, AuthUser +from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator from rhodecode.lib.base import BaseController, render from rhodecode.lib.celerylib import tasks, run_task from rhodecode.lib.exceptions import HgsubversionImportError -from rhodecode.lib.utils import repo2db_mapper, set_rhodecode_config, \ - check_git_version -from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \ - RhodeCodeSetting, PullRequest, PullRequestReviewers -from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \ +from rhodecode.lib.utils import repo2db_mapper, set_rhodecode_config +from rhodecode.model.db import RhodeCodeUi, Repository, RhodeCodeSetting +from rhodecode.model.forms import ApplicationSettingsForm, \ ApplicationUiSettingsForm, ApplicationVisualisationForm -from rhodecode.model.scm import ScmModel, RepoGroupList -from rhodecode.model.user import UserModel -from rhodecode.model.repo import RepoModel -from rhodecode.model.db import User +from rhodecode.model.license import LicenseModel +from rhodecode.model.scm import ScmModel from rhodecode.model.notification import EmailNotificationModel from rhodecode.model.meta import Session -from rhodecode.lib.utils2 import str2bool, safe_unicode +from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str from rhodecode.lib.compat import json log = logging.getLogger(__name__) @@ -69,168 +61,37 @@ @LoginRequired() def __before__(self): super(SettingsController, self).__before__() - c.modules = sorted([(p.project_name, p.version) - for p in pkg_resources.working_set] - + [('git', check_git_version())], - key=lambda k: k[0].lower()) - c.py_version = platform.python_version() - c.platform = platform.platform() - @HasPermissionAllDecorator('hg.admin') - def index(self, format='html'): - """GET /admin/settings: All items in the collection""" - # url('admin_settings') - - defaults = RhodeCodeSetting.get_app_settings() - defaults.update(self._get_hg_ui_settings()) + def _get_hg_ui_settings(self): + ret = RhodeCodeUi.query().all() - return htmlfill.render( - render('admin/settings/settings.html'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False - ) + 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' - @HasPermissionAllDecorator('hg.admin') - def create(self): - """POST /admin/settings: Create a new item""" - # url('admin_settings') + if k == 'push_ssl': + v = str2bool(v) + + if k.find('.') != -1: + k = k.replace('.', '_') - @HasPermissionAllDecorator('hg.admin') - def new(self, format='html'): - """GET /admin/settings/new: Form to create a new item""" - # url('admin_new_setting') + if each.ui_section in ['hooks', 'extensions']: + v = each.ui_active + + settings[each.ui_section + '_' + k] = v + return settings @HasPermissionAllDecorator('hg.admin') - def update(self, setting_id): - """PUT /admin/settings/setting_id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('admin_setting', setting_id=ID), - # method='put') - # url('admin_setting', setting_id=ID) - - if setting_id == 'mapping': - rm_obsolete = request.POST.get('destroy', False) - invalidate_cache = request.POST.get('invalidate', False) - log.debug('rescanning repo location with destroy obsolete=%s' - % (rm_obsolete,)) - - if invalidate_cache: - log.debug('invalidating all repositories cache') - for repo in Repository.get_all(): - ScmModel().mark_for_invalidation(repo.repo_name) - - filesystem_repos = ScmModel().repo_scan() - added, removed = repo2db_mapper(filesystem_repos, rm_obsolete) - _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-' - h.flash(_('Repositories successfully ' - 'rescanned added: %s ; removed: %s') % - (_repr(added), _repr(removed)), - category='success') - - if setting_id == 'whoosh': - repo_location = self._get_hg_ui_settings()['paths_root_path'] - full_index = request.POST.get('full_index', False) - run_task(tasks.whoosh_index, repo_location, full_index) - h.flash(_('Whoosh reindex task scheduled'), category='success') - - if setting_id == 'global': - - 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 = RhodeCodeSetting.get_by_name_or_create('title') - sett1.app_settings_value = form_result['rhodecode_title'] - Session().add(sett1) - - sett2 = RhodeCodeSetting.get_by_name_or_create('realm') - sett2.app_settings_value = form_result['rhodecode_realm'] - Session().add(sett2) - - sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code') - sett3.app_settings_value = form_result['rhodecode_ga_code'] - Session().add(sett3) - - Session().commit() - set_rhodecode_config(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') - - if setting_id == 'visual': - - 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: - #TODO: rewrite this to something less ugly - sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon') - sett1.app_settings_value = \ - form_result['rhodecode_show_public_icon'] - Session().add(sett1) - - sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon') - sett2.app_settings_value = \ - form_result['rhodecode_show_private_icon'] - Session().add(sett2) - - sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags') - sett3.app_settings_value = \ - form_result['rhodecode_stylify_metatags'] - Session().add(sett3) - - sett4 = RhodeCodeSetting.get_by_name_or_create('repository_fields') - sett4.app_settings_value = \ - form_result['rhodecode_repository_fields'] - Session().add(sett4) - - sett5 = RhodeCodeSetting.get_by_name_or_create('dashboard_items') - sett5.app_settings_value = \ - form_result['rhodecode_dashboard_items'] - Session().add(sett5) - - sett6 = RhodeCodeSetting.get_by_name_or_create('show_version') - sett6.app_settings_value = \ - form_result['rhodecode_show_version'] - Session().add(sett6) - - Session().commit() - set_rhodecode_config(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') - - if setting_id == 'vcs': + 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)) @@ -290,7 +151,7 @@ sett.ui_active = form_result['extensions_hgsubversion'] if sett.ui_active: try: - import hgsubversion + import hgsubversion # pragma: no cover except ImportError: raise HgsubversionImportError Session().add(sett) @@ -320,16 +181,225 @@ h.flash(_('Error occurred during updating ' 'application settings'), category='error') - if setting_id == 'hooks': + defaults = RhodeCodeSetting.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 = RhodeCodeSetting.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 = RhodeCodeSetting.create_or_update('title', + form_result['rhodecode_title']) + Session().add(sett1) + + sett2 = RhodeCodeSetting.create_or_update('realm', + form_result['rhodecode_realm']) + Session().add(sett2) + + sett3 = RhodeCodeSetting.create_or_update('ga_code', + form_result['rhodecode_ga_code']) + Session().add(sett3) + + sett4 = RhodeCodeSetting.create_or_update('captcha_public_key', + form_result['rhodecode_captcha_public_key']) + Session().add(sett4) + + sett5 = RhodeCodeSetting.create_or_update('captcha_private_key', + form_result['rhodecode_captcha_private_key']) + Session().add(sett5) + + Session().commit() + set_rhodecode_config(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 = RhodeCodeSetting.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 = RhodeCodeSetting.create_or_update(setting, + form_result[form_key], type_) + Session().add(sett) + + Session().commit() + set_rhodecode_config(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 = RhodeCodeSetting.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.rhodecode_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 = RhodeCodeSetting.get_app_settings() + defaults.update(self._get_hg_ui_settings()) + + import rhodecode + c.rhodecode_ini = rhodecode.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: RhodeCodeUi.create_or_update_hook(ui_key, ui_value) - h.flash(_('Added new hook'), - category='success') + h.flash(_('Added new hook'), category='success') + elif hook_id: + RhodeCodeUi.delete(hook_id) + Session().commit() # check for edits update = False @@ -347,190 +417,163 @@ h.flash(_('Error occurred during hook creation'), category='error') - return redirect(url('admin_edit_setting', setting_id='hooks')) - - if setting_id == 'email': - test_email = request.POST.get('test_email') - test_email_subj = 'RhodeCode TestEmail' - test_email_body = 'RhodeCode Email test' - - 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(_('Email task created'), category='success') - return redirect(url('admin_settings')) + return redirect(url('admin_settings_hooks')) - @HasPermissionAllDecorator('hg.admin') - def delete(self, setting_id): - """DELETE /admin/settings/setting_id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('admin_setting', setting_id=ID), - # method='delete') - # url('admin_setting', setting_id=ID) - if setting_id == 'hooks': - hook_id = request.POST.get('hook_id') - RhodeCodeUi.delete(hook_id) - Session().commit() - - @HasPermissionAllDecorator('hg.admin') - def show(self, setting_id, format='html'): - """ - GET /admin/settings/setting_id: Show a specific item""" - # url('admin_setting', setting_id=ID) - - @HasPermissionAllDecorator('hg.admin') - def edit(self, setting_id, format='html'): - """ - GET /admin/settings/setting_id/edit: Form to - edit an existing item""" - # url('admin_edit_setting', setting_id=ID) + defaults = RhodeCodeSetting.get_app_settings() + defaults.update(self._get_hg_ui_settings()) c.hooks = RhodeCodeUi.get_builtin_hooks() c.custom_hooks = RhodeCodeUi.get_custom_hooks() return htmlfill.render( - render('admin/settings/hooks.html'), - defaults={}, + render('admin/settings/settings.html'), + defaults=defaults, encoding="UTF-8", - force_defaults=False - ) + force_defaults=False) - def _load_my_repos_data(self): - repos_list = Session().query(Repository)\ - .filter(Repository.user_id == - self.rhodecode_user.user_id)\ - .order_by(func.lower(Repository.repo_name)).all() + @HasPermissionAllDecorator('hg.admin') + def settings_search(self): + """GET /admin/settings/search: All items in the collection""" + # url('admin_settings_search') + c.active = 'search' + if request.POST: + repo_location = self._get_hg_ui_settings()['paths_root_path'] + full_index = request.POST.get('full_index', False) + run_task(tasks.whoosh_index, repo_location, full_index) + h.flash(_('Whoosh reindex task scheduled'), category='success') + return redirect(url('admin_settings_search')) - repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, - admin=True) - #json used to render the grid - return json.dumps(repos_data) + defaults = RhodeCodeSetting.get_app_settings() + defaults.update(self._get_hg_ui_settings()) - @NotAnonymous() - def my_account(self): - """ - GET /_admin/my_account Displays info about my account - """ - # url('admin_settings_my_account') + return htmlfill.render( + render('admin/settings/settings.html'), + defaults=defaults, + encoding="UTF-8", + force_defaults=False) - c.user = User.get(self.rhodecode_user.user_id) - c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id, - ip_addr=self.ip_addr) - c.ldap_dn = c.user.ldap_dn + @HasPermissionAllDecorator('hg.admin') + def settings_system(self): + """GET /admin/settings/system: All items in the collection""" + # url('admin_settings_system') + c.active = 'system' - if c.user.username == 'default': - h.flash(_("You can't edit this user since it's" - " crucial for entire application"), category='warning') - return redirect(url('users')) + defaults = RhodeCodeSetting.get_app_settings() + defaults.update(self._get_hg_ui_settings()) - #json used to render the grid - c.data = self._load_my_repos_data() + import rhodecode + c.rhodecode_ini = rhodecode.CONFIG + c.rhodecode_update_url = defaults.get('rhodecode_update_url') + server_info = RhodeCodeSetting.get_server_info() + for key, val in server_info.iteritems(): + setattr(c, key, val) - defaults = c.user.get_dict() - - c.form = htmlfill.render( - render('admin/users/user_edit_my_account_form.html'), + return htmlfill.render( + render('admin/settings/settings.html'), defaults=defaults, encoding="UTF-8", - force_defaults=False - ) - return render('admin/users/user_edit_my_account.html') + force_defaults=False) - @NotAnonymous() - def my_account_update(self): - """PUT /_admin/my_account_update: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('admin_settings_my_account_update'), - # method='put') - # url('admin_settings_my_account_update', id=ID) - uid = self.rhodecode_user.user_id - c.user = User.get(self.rhodecode_user.user_id) - c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id, - ip_addr=self.ip_addr) - c.ldap_dn = c.user.ldap_dn - email = self.rhodecode_user.email - _form = UserForm(edit=True, - old_data={'user_id': uid, 'email': email})() - form_result = {} + @HasPermissionAllDecorator('hg.admin') + def settings_system_update(self): + """GET /admin/settings/system/updates: All items in the collection""" + # url('admin_settings_system_update') + import json + import urllib2 + from rhodecode.lib.verlib import NormalizedVersion + from rhodecode import __version__ + + defaults = RhodeCodeSetting.get_app_settings() + defaults.update(self._get_hg_ui_settings()) + _update_url = defaults.get('rhodecode_update_url', '') + + _err = lambda s: '
%s
' % (s) try: - form_result = _form.to_python(dict(request.POST)) - skip_attrs = ['admin', 'active'] # skip attr for my account - if c.ldap_dn: - #forbid updating username for ldap accounts - skip_attrs.append('username') - UserModel().update(uid, form_result, skip_attrs=skip_attrs) - h.flash(_('Your account was updated successfully'), - category='success') - Session().commit() - except formencode.Invalid, errors: - #json used to render the grid - c.data = self._load_my_repos_data() - c.form = htmlfill.render( - render('admin/users/user_edit_my_account_form.html'), - defaults=errors.value, - errors=errors.error_dict or {}, - prefix_error=False, - encoding="UTF-8") - return render('admin/users/user_edit_my_account.html') - except Exception: + import rhodecode + ver = rhodecode.__version__ + log.debug('Checking for upgrade on `%s` server' % _update_url) + opener = urllib2.build_opener() + opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)] + response = opener.open(_update_url) + response_data = response.read() + data = json.loads(response_data) + except urllib2.URLError, e: + log.error(traceback.format_exc()) + return _err('Failed to contact upgrade server: %r' % e) + except ValueError, e: log.error(traceback.format_exc()) - h.flash(_('Error occurred during update of user %s') \ - % form_result.get('username'), category='error') + return _err('Bad data sent from update server') + + latest = data['versions'][0] + + c.update_url = _update_url + c.latest_data = latest + c.latest_ver = latest['version'] + c.cur_ver = __version__ + c.should_upgrade = False + + if NormalizedVersion(c.latest_ver) > NormalizedVersion(c.cur_ver): + c.should_upgrade = True + c.important_notices = latest['general'] + + return render('admin/settings/settings_system_update.html'), - return redirect(url('my_account')) - - @NotAnonymous() - def my_account_my_pullrequests(self): - 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 + @HasPermissionAllDecorator('hg.admin') + def settings_license(self): + """GET /admin/settings/hooks: All items in the collection""" + # url('admin_settings_license') + c.active = 'license' + if request.POST: + form_result = request.POST + try: + sett1 = RhodeCodeSetting.create_or_update('license_key', + form_result['rhodecode_license_key'], + 'unicode') + Session().add(sett1) + Session().commit() + set_rhodecode_config(config) + h.flash(_('Updated license information'), + category='success') - c.my_pull_requests = _filter(PullRequest.query()\ - .filter(PullRequest.user_id == - self.rhodecode_user.user_id)\ - .all()) + except Exception: + log.error(traceback.format_exc()) + h.flash(_('Error occurred during updating license info'), + category='error') - c.participate_in_pull_requests = _filter([ - x.pull_request for x in PullRequestReviewers.query()\ - .filter(PullRequestReviewers.user_id == - self.rhodecode_user.user_id).all()]) + return redirect(url('admin_settings_license')) - return render('admin/users/user_edit_my_account_pullrequests.html') + defaults = RhodeCodeSetting.get_app_settings() + defaults.update(self._get_hg_ui_settings()) - def _get_hg_ui_settings(self): - ret = RhodeCodeUi.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' + import rhodecode + c.rhodecode_ini = rhodecode.CONFIG + c.license_token = c.rhodecode_ini.get('license_token') + c.generated_license_token = LicenseModel.generate_license_token() + c.license_info = {} + c.license_loaded = False + # try to read info about license + try: + license_key = defaults.get('rhodecode_license_key') + if c.license_token and license_key: + c.license_info = json.loads( + LicenseModel(key=c.license_token).decrypt(license_key)) + expires = h.fmt_date(h.time_to_datetime(c.license_info['valid_till'])) + now = time.time() + if 0 < (c.license_info['valid_till'] - now) < 60*60*24*7: + h.flash(_('Your license will expire on %s, please contact ' + 'support to extend your license.' % expires), category='warning') + if c.license_info['valid_till'] - now < 0: + h.flash(_('Your license has expired on %s, please contact ' + 'support to extend your license.' % expires), category='error') + c.license_loaded = True + except Exception, e: + log.error(traceback.format_exc()) + h.flash(_('Unexpected error while reading license key. Please ' + 'make sure your license token and key are correct'), + category='error') - 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 + return htmlfill.render( + render('admin/settings/settings.html'), + defaults=defaults, + encoding="UTF-8", + force_defaults=False) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/user_groups.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/controllers/admin/user_groups.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,461 @@ +# -*- 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 . +""" +rhodecode.controllers.admin.users_groups +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +User Groups crud controller for pylons + +:created_on: Jan 25, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE 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 rhodecode +from rhodecode.lib import helpers as h +from rhodecode.lib.exceptions import UserGroupsAssignedException,\ + RepoGroupAssignmentError +from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int +from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\ + HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator +from rhodecode.lib.base import BaseController, render +from rhodecode.model.scm import UserGroupList +from rhodecode.model.user_group import UserGroupModel +from rhodecode.model.repo import RepoModel +from rhodecode.model.db import User, UserGroup, UserGroupToPerm,\ + UserGroupRepoToPerm, UserGroupRepoGroupToPerm +from rhodecode.model.forms import UserGroupForm, UserGroupPermsForm,\ + CustomDefaultPermissionsForm +from rhodecode.model.meta import Session +from rhodecode.lib.utils import action_logger +from rhodecode.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 = rhodecode.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.rhodecode_user.user_id, + active=form_result['users_group_active']) + + gr = form_result['users_group_name'] + action_logger(self.rhodecode_user, + '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: + # + # 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.rhodecode_user, + '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: + # + # 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.rhodecode_user, '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.rhodecode_user.is_admin: + if obj_type == 'user' and c.rhodecode_user.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) + + try: + form = CustomDefaultPermissionsForm()() + form_result = form.to_python(request.POST) + + inherit_perms = form_result['inherit_default_permissions'] + user_group.inherit_default_permissions = inherit_perms + Session().add(user_group) + usergroup_model = UserGroupModel() + + defs = UserGroupToPerm.query()\ + .filter(UserGroupToPerm.users_group == user_group)\ + .all() + for ug in defs: + Session().delete(ug) + + if form_result['create_repo_perm']: + usergroup_model.grant_perm(id, 'hg.create.repository') + else: + usergroup_model.grant_perm(id, 'hg.create.none') + if form_result['create_user_group_perm']: + usergroup_model.grant_perm(id, 'hg.usergroup.create.true') + else: + usergroup_model.grant_perm(id, 'hg.usergroup.create.false') + if form_result['fork_repo_perm']: + usergroup_model.grant_perm(id, 'hg.fork.repository') + else: + usergroup_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_group_default_perms', id=id)) + + @HasUserGroupPermissionAnyDecorator('usergroup.admin') + def edit_advanced(self, id): + c.user_group = UserGroup.get_or_404(id) + c.active = 'advanced' + c.group_members_obj = sorted((x.user for x in c.user_group.members), + key=lambda u: u.username.lower()) + return render('admin/user_groups/user_group_edit.html') + + + @HasUserGroupPermissionAnyDecorator('usergroup.admin') + def edit_members(self, id): + c.user_group = UserGroup.get_or_404(id) + c.active = 'members' + 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] + return render('admin/user_groups/user_group_edit.html') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/users.py --- a/rhodecode/controllers/admin/users.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/admin/users.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.users - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Users crud controller for pylons - - :created_on: Apr 4, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.admin.users +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Users crud controller for pylons + +:created_on: Apr 4, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import traceback @@ -32,14 +32,18 @@ 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 rhodecode from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException, UserCreationError from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ - AuthUser + AuthUser, generate_api_key +import rhodecode.lib.auth_modules.auth_rhodecode +from rhodecode.lib import auth_modules from rhodecode.lib.base import BaseController, render +from rhodecode.model.api_key import ApiKeyModel from rhodecode.model.db import User, UserEmailMap, UserIpMap, UserToPerm from rhodecode.model.forms import UserForm, CustomDefaultPermissionsForm @@ -47,16 +51,13 @@ from rhodecode.model.meta import Session from rhodecode.lib.utils import action_logger from rhodecode.lib.compat import json -from rhodecode.lib.utils2 import datetime_to_time, str2bool +from rhodecode.lib.utils2 import datetime_to_time, str2bool, safe_int log = logging.getLogger(__name__) class UsersController(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('user', 'users') @LoginRequired() @HasPermissionAllDecorator('hg.admin') @@ -70,6 +71,7 @@ c.users_list = User.query().order_by(User.username)\ .filter(User.username != User.DEFAULT_USER)\ + .order_by(func.lower(User.username))\ .all() users_data = [] @@ -81,7 +83,7 @@ template.get_def("user_gravatar") .render(user_email, size, _=_, h=h, c=c)) - user_lnk = lambda user_id, username: ( + username = lambda user_id, username: ( template.get_def("user_name") .render(user_id, username, _=_, h=h, c=c)) @@ -92,16 +94,17 @@ for user in c.users_list: users_data.append({ - "gravatar": grav_tmpl(user. email, 24), - "raw_username": user.username, - "username": user_lnk(user.user_id, user.username), + "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), - "ldap": h.boolicon(bool(user.ldap_dn)), + "extern_type": user.extern_type, + "extern_name": user.extern_name, "action": user_actions(user.user_id, user.username), }) @@ -118,7 +121,7 @@ def create(self): """POST /users: Create a new item""" # url('users') - + c.default_extern_type = auth_modules.auth_rhodecode.RhodeCodeAuthPlugin.name user_model = UserModel() user_form = UserForm()() try: @@ -148,6 +151,7 @@ def new(self, format='html'): """GET /users/new: Form to create a new item""" # url('new_user') + c.default_extern_type = auth_modules.auth_rhodecode.RhodeCodeAuthPlugin.name return render('admin/users/user_add.html') def update(self, id): @@ -158,19 +162,23 @@ # 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.ldap_dn = c.user.ldap_dn + 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 = [] - if c.ldap_dn: - #forbid updating username for ldap accounts - skip_attrs = ['username'] + skip_attrs = ['extern_type', 'extern_name'] + #TODO: plugin should define if username can be updated + if c.extern_type != "rhodecode": + # 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.rhodecode_user, 'admin_updated_user:%s' % usr, @@ -178,14 +186,11 @@ h.flash(_('User updated successfully'), category='success') Session().commit() except formencode.Invalid, errors: - c.user_email_map = UserEmailMap.query()\ - .filter(UserEmailMap.user == c.user).all() - c.user_ip_map = UserIpMap.query()\ - .filter(UserIpMap.user == c.user).all() defaults = errors.value e = errors.error_dict or {} defaults.update({ - 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'), + 'create_repo_perm': user_model.has_perm(id, + 'hg.create.repository'), 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'), '_method': 'put' }) @@ -231,36 +236,131 @@ """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')) - if c.user.username == 'default': + 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) - c.user.permissions = {} - c.granted_permissions = UserModel().fill_perms(c.user)\ - .permissions['global'] - c.user_email_map = UserEmailMap.query()\ - .filter(UserEmailMap.user == c.user).all() - c.user_ip_map = UserIpMap.query()\ - .filter(UserIpMap.user == c.user).all() + umodel = UserModel() - c.ldap_dn = c.user.ldap_dn 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'), + '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 - ) + 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')) - def update_perm(self, id): + 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) @@ -298,7 +398,24 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during permissions saving'), category='error') - return redirect(url('edit_user', id=id)) + 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') + return redirect(url('users')) + + c.active = 'emails' + c.user_email_map = UserEmailMap.query()\ + .filter(UserEmailMap.user == c.user).all() + + defaults = c.user.get_dict() + return htmlfill.render( + render('admin/users/user_edit.html'), + defaults=defaults, + encoding="UTF-8", + force_defaults=False) def add_email(self, id): """POST /user_emails:Add an existing item""" @@ -318,16 +435,38 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during email saving'), category='error') - return redirect(url('edit_user', id=id)) + return redirect(url('edit_user_emails', id=id)) def delete_email(self, id): """DELETE /user_emails_delete/id: Delete an existing item""" # url('user_emails_delete', id=ID, method='delete') + email_id = request.POST.get('del_email_id') user_model = UserModel() - user_model.delete_extra_email(id, request.POST.get('del_email')) + user_model.delete_extra_email(id, email_id) Session().commit() h.flash(_("Removed email from user"), category='success') - return redirect(url('edit_user', id=id)) + return redirect(url('edit_user_emails', id=id)) + + def edit_ips(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 = 'ips' + c.user_ip_map = UserIpMap.query()\ + .filter(UserIpMap.user == c.user).all() + + c.inherit_default_ips = c.user.inherit_default_permissions + c.default_user_ip_map = UserIpMap.query()\ + .filter(UserIpMap.user == User.get_default_user()).all() + + defaults = c.user.get_dict() + return htmlfill.render( + render('admin/users/user_edit.html'), + defaults=defaults, + encoding="UTF-8", + force_defaults=False) def add_ip(self, id): """POST /user_ips:Add an existing item""" @@ -339,7 +478,7 @@ try: user_model.add_extra_ip(id, ip) Session().commit() - h.flash(_("Added ip %s to user") % ip, category='success') + h.flash(_("Added ip %s to user whitelist") % ip, category='success') except formencode.Invalid, error: msg = error.error_dict['ip'] h.flash(msg, category='error') @@ -347,17 +486,20 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during ip saving'), category='error') + if 'default_user' in request.POST: - return redirect(url('edit_permission', id='default')) - return redirect(url('edit_user', id=id)) + return redirect(url('admin_permissions_ips')) + return redirect(url('edit_user_ips', id=id)) def delete_ip(self, id): """DELETE /user_ips_delete/id: Delete an existing item""" # url('user_ips_delete', id=ID, method='delete') + ip_id = request.POST.get('del_ip_id') user_model = UserModel() - user_model.delete_extra_ip(id, request.POST.get('del_ip')) + user_model.delete_extra_ip(id, ip_id) Session().commit() - h.flash(_("Removed ip from user"), category='success') + h.flash(_("Removed ip address from user whitelist"), category='success') + if 'default_user' in request.POST: - return redirect(url('edit_permission', id='default')) - return redirect(url('edit_user', id=id)) + return redirect(url('admin_permissions_ips')) + return redirect(url('edit_user_ips', id=id)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/admin/users_groups.py --- a/rhodecode/controllers/admin/users_groups.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,379 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.controllers.admin.users_groups - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - User Groups crud controller for pylons - - :created_on: Jan 25, 2011 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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 . - -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 rhodecode.lib import helpers as h -from rhodecode.lib.exceptions import UserGroupsAssignedException,\ - RepoGroupAssignmentError -from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int -from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\ - HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator -from rhodecode.lib.base import BaseController, render -from rhodecode.model.scm import UserGroupList -from rhodecode.model.users_group import UserGroupModel -from rhodecode.model.repo import RepoModel -from rhodecode.model.db import User, UserGroup, UserGroupToPerm,\ - UserGroupRepoToPerm, UserGroupRepoGroupToPerm -from rhodecode.model.forms import UserGroupForm, UserGroupPermsForm,\ - CustomDefaultPermissionsForm -from rhodecode.model.meta import Session -from rhodecode.lib.utils import action_logger -from sqlalchemy.orm import joinedload -from webob.exc import HTTPInternalServerError - -log = logging.getLogger(__name__) - - -class UsersGroupsController(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('users_group', 'users_groups') - - @LoginRequired() - def __before__(self): - super(UsersGroupsController, self).__before__() - c.available_permissions = config['available_permissions'] - - def __load_data(self, user_group_id): - permissions = { - 'repositories': {}, - 'repositories_groups': {} - } - ugroup_repo_perms = UserGroupRepoToPerm.query()\ - .options(joinedload(UserGroupRepoToPerm.permission))\ - .options(joinedload(UserGroupRepoToPerm.repository))\ - .filter(UserGroupRepoToPerm.users_group_id == user_group_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 == user_group_id)\ - .all() - - for gr in ugroup_group_perms: - permissions['repositories_groups'][gr.group.group_name] \ - = gr.permission.permission_name - c.permissions = permissions - c.group_members_obj = sorted((x.user for x in c.users_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()) - repo_model = RepoModel() - c.users_array = repo_model.get_users_js() - c.users_groups_array = repo_model.get_users_groups_js() - c.available_permissions = config['available_permissions'] - - 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() - - ug_model = UserGroupModel() - - data.update({ - 'create_repo_perm': ug_model.has_perm(user_group, - 'hg.create.repository'), - 'create_user_group_perm': ug_model.has_perm(user_group, - 'hg.usergroup.create.true'), - 'fork_repo_perm': ug_model.has_perm(user_group, - 'hg.fork.repository'), - }) - - # fill user group users - for p in user_group.user_user_group_to_perm: - data.update({'u_perm_%s' % p.user.username: - p.permission.permission_name}) - - for p in user_group.user_group_user_group_to_perm: - data.update({'g_perm_%s' % p.user_group.users_group_name: - p.permission.permission_name}) - - return data - - def index(self, format='html'): - """GET /users_groups: All items in the collection""" - # url('users_groups') - - group_iter = UserGroupList(UserGroup().query().all(), - perm_set=['usergroup.admin']) - sk = lambda g: g.users_group_name - c.users_groups_list = sorted(group_iter, key=sk) - return render('admin/users_groups/users_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'], - owner=self.rhodecode_user.user_id, - active=form_result['users_group_active']) - - gr = form_result['users_group_name'] - action_logger(self.rhodecode_user, - '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/users_groups/users_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 /users_groups/new: Form to create a new item""" - # url('new_users_group') - return render('admin/users_groups/users_group_add.html') - - @HasUserGroupPermissionAnyDecorator('usergroup.admin') - def update(self, id): - """PUT /users_groups/id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('users_group', id=ID), - # method='put') - # url('users_group', id=ID) - - c.users_group = UserGroup.get_or_404(id) - 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.users_group.get_dict(), - available_members=available_members)() - - try: - form_result = users_group_form.to_python(request.POST) - UserGroupModel().update(c.users_group, form_result) - gr = form_result['users_group_name'] - action_logger(self.rhodecode_user, - '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/users_groups/users_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 /users_groups/id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # 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')) - - @HasUserGroupPermissionAnyDecorator('usergroup.admin') - def set_user_group_perm_member(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_users_group', id=id)) - #TODO: implement this - #action_logger(self.rhodecode_user, '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_users_group', id=id)) - - @HasUserGroupPermissionAnyDecorator('usergroup.admin') - def delete_user_group_perm_member(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.rhodecode_user.is_admin: - if obj_type == 'user' and c.rhodecode_user.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_users_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() - - def show(self, id, format='html'): - """GET /users_groups/id: Show a specific item""" - # url('users_group', id=ID) - - @HasUserGroupPermissionAnyDecorator('usergroup.admin') - def edit(self, id, format='html'): - """GET /users_groups/id/edit: Form to edit an existing item""" - # url('edit_users_group', id=ID) - - c.users_group = UserGroup.get_or_404(id) - self.__load_data(id) - - defaults = self.__load_defaults(id) - - return htmlfill.render( - render('admin/users_groups/users_group_edit.html'), - defaults=defaults, - encoding="UTF-8", - force_defaults=False - ) - - @HasUserGroupPermissionAnyDecorator('usergroup.admin') - def update_perm(self, id): - """PUT /users_perm/id: Update an existing item""" - # url('users_group_perm', id=ID, method='put') - - users_group = UserGroup.get_or_404(id) - - try: - form = CustomDefaultPermissionsForm()() - form_result = form.to_python(request.POST) - - inherit_perms = form_result['inherit_default_permissions'] - users_group.inherit_default_permissions = inherit_perms - Session().add(users_group) - usergroup_model = UserGroupModel() - - defs = UserGroupToPerm.query()\ - .filter(UserGroupToPerm.users_group == users_group)\ - .all() - for ug in defs: - Session().delete(ug) - - if form_result['create_repo_perm']: - usergroup_model.grant_perm(id, 'hg.create.repository') - else: - usergroup_model.grant_perm(id, 'hg.create.none') - if form_result['create_user_group_perm']: - usergroup_model.grant_perm(id, 'hg.usergroup.create.true') - else: - usergroup_model.grant_perm(id, 'hg.usergroup.create.false') - if form_result['fork_repo_perm']: - usergroup_model.grant_perm(id, 'hg.fork.repository') - else: - usergroup_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_users_group', id=id)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/api/__init__.py --- a/rhodecode/controllers/api/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/api/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,19 +1,8 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.api - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - JSON RPC controller - - :created_on: Aug 20, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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; version 2 -# of the License or (at your opinion) any later version of the license. +# 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 @@ -21,9 +10,18 @@ # 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, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. +# along with this program. If not, see . +""" +rhodecode.controllers.api +~~~~~~~~~~~~~~~~~~~~~~~~~ + +JSON RPC controller + +:created_on: Aug 20, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import inspect import logging @@ -35,15 +33,14 @@ from paste.response import replace_header from pylons.controllers import WSGIController -from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ -HTTPBadRequest, HTTPError +from webob.exc import HTTPError from rhodecode.model.db import User from rhodecode.model import meta from rhodecode.lib.compat import izip_longest, json from rhodecode.lib.auth import AuthUser -from rhodecode.lib.base import _get_ip_addr, _get_access_path -from rhodecode.lib.utils2 import safe_unicode +from rhodecode.lib.base import _get_ip_addr as _get_ip, _get_access_path +from rhodecode.lib.utils2 import safe_unicode, safe_str log = logging.getLogger('JSONRPC') @@ -55,18 +52,22 @@ super(JSONRPCError, self).__init__() def __str__(self): - return str(self.message) + 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' + body=json.dumps(dict(id=retid, result=None, error=message)), + status=code, + content_type='application/json' ) @@ -85,7 +86,7 @@ """ def _get_ip_addr(self, environ): - return _get_ip_addr(environ) + return _get_ip(environ) def _get_method_args(self): """ @@ -99,6 +100,12 @@ 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 @@ -119,12 +126,12 @@ raw_body = environ['wsgi.input'].read(length) try: - json_body = json.loads(urllib.unquote_plus(raw_body)) + 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, urllib.unquote_plus(raw_body))) + message="JSON parse error ERR:%s RAW:%r" + % (e, raw_body)) # check AUTH based on API KEY try: @@ -154,9 +161,9 @@ 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)) + message='request from IP:%s not allowed' % (ip_addr,)) else: - log.info('Access for IP:%s allowed' % (ip_addr)) + log.info('Access for IP:%s allowed' % (ip_addr,)) except Exception, e: return jsonrpc_error(retid=self._req_id, @@ -206,7 +213,7 @@ # 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): + if default == default_empty and arg not in self._request_params: return jsonrpc_error( retid=self._req_id, message=( @@ -237,7 +244,7 @@ replace_header(headers, 'Content-Type', 'application/json') start_response(status[0], headers, exc_info[0]) log.info('IP: %s Request to %s time: %.3fs' % ( - _get_ip_addr(environ), + self._get_ip_addr(environ), safe_unicode(_get_access_path(environ)), time.time() - start) ) return output @@ -246,24 +253,23 @@ """ 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 = str(e) + self._error = safe_str(e) except Exception, e: - log.error('Encountered unhandled exception: %s' \ - % traceback.format_exc()) + log.error('Encountered unhandled exception: %s' + % (traceback.format_exc(),)) json_exc = JSONRPCError('Internal server error') - self._error = str(json_exc) + 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) - + response = dict(id=self._req_id, result=raw_response, error=self._error) try: return json.dumps(response) except TypeError, e: @@ -280,7 +286,7 @@ """ Return method named by `self._req_method` in controller if able """ - log.debug('Trying to find JSON-RPC method: %s' % self._req_method) + log.debug('Trying to find JSON-RPC method: %s' % (self._req_method,)) if self._req_method.startswith('_'): raise AttributeError("Method not allowed") @@ -293,4 +299,4 @@ if isinstance(func, types.MethodType): return func else: - raise AttributeError("No such method: %s" % self._req_method) + raise AttributeError("No such method: %s" % (self._req_method,)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/api/api.py --- a/rhodecode/controllers/api/api.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/api/api.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,19 +1,8 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.api - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - API controller for RhodeCode - - :created_on: Aug 20, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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; version 2 -# of the License or (at your opinion) any later version of the license. +# 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 @@ -21,31 +10,46 @@ # 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, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. +# along with this program. If not, see . +""" +rhodecode.controllers.api +~~~~~~~~~~~~~~~~~~~~~~~~~ + +API controller for RhodeCode + +:created_on: Aug 20, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import time import traceback import logging +from sqlalchemy import or_ from rhodecode.controllers.api import JSONRPCController, JSONRPCError -from rhodecode.lib.auth import PasswordGenerator, AuthUser, \ - HasPermissionAllDecorator, HasPermissionAnyDecorator, \ - HasPermissionAnyApi, HasRepoPermissionAnyApi +from rhodecode.lib.auth import ( + PasswordGenerator, AuthUser, HasPermissionAllDecorator, + HasPermissionAnyDecorator, HasPermissionAnyApi, HasRepoPermissionAnyApi, + HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAny) from rhodecode.lib.utils import map_groups, repo2db_mapper -from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int +from rhodecode.lib.utils2 import ( + str2bool, time_to_datetime, safe_int, Optional, OAttr) from rhodecode.model.meta import Session -from rhodecode.model.scm import ScmModel +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.scm import ScmModel, UserGroupList from rhodecode.model.repo import RepoModel from rhodecode.model.user import UserModel -from rhodecode.model.users_group import UserGroupModel -from rhodecode.model.repos_group import ReposGroupModel -from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\ - Permission, User, Gist +from rhodecode.model.user_group import UserGroupModel +from rhodecode.model.gist import GistModel +from rhodecode.model.db import ( + Repository, RhodeCodeSetting, UserIpMap, Permission, User, Gist, + RepoGroup) from rhodecode.lib.compat import json -from rhodecode.lib.exceptions import DefaultUserException -from rhodecode.model.gist import GistModel +from rhodecode.lib.exceptions import ( + DefaultUserException, UserGroupsAssignedException) log = logging.getLogger(__name__) @@ -59,56 +63,6 @@ updates[name] = attr -class OptionalAttr(object): - """ - Special Optional Option that defines other attribute - """ - def __init__(self, attr_name): - self.attr_name = attr_name - - def __repr__(self): - return '' % self.attr_name - - def __call__(self): - return self -#alias -OAttr = OptionalAttr - - -class Optional(object): - """ - Defines an optional parameter:: - - param = param.getval() if isinstance(param, Optional) else param - param = param() if isinstance(param, Optional) else param - - is equivalent of:: - - param = Optional.extract(param) - - """ - def __init__(self, type_): - self.type_ = type_ - - def __repr__(self): - return '' % self.type_.__repr__() - - def __call__(self): - return self.getval() - - def getval(self): - """ - returns value from this Optional instance - """ - return self.type_ - - @classmethod - def extract(cls, val): - if isinstance(val, cls): - return val.getval() - return val - - def get_user_or_error(userid): """ Get user by id or name or return JsonRPCError if not found @@ -117,7 +71,7 @@ """ user = UserModel().get_user(userid) if user is None: - raise JSONRPCError("user `%s` does not exist" % userid) + raise JSONRPCError("user `%s` does not exist" % (userid,)) return user @@ -129,7 +83,7 @@ """ repo = RepoModel().get_repo(repoid) if repo is None: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) return repo @@ -139,60 +93,103 @@ :param repogroupid: """ - repo_group = ReposGroupModel()._get_repo_group(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_users_group_or_error(usersgroupid): +def get_user_group_or_error(usergroupid): """ Get user group by id or name or return JsonRPCError if not found - :param userid: + :param usergroupid: """ - users_group = UserGroupModel().get_group(usersgroupid) - if users_group is None: - raise JSONRPCError('user group `%s` does not exist' % usersgroupid) - return users_group + 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): +def get_perm_or_error(permid, prefix=None): """ Get permission by id or name or return JsonRPCError if not found - :param userid: + :param permid: """ perm = Permission.get_by_key(permid) if perm is None: - raise JSONRPCError('permission `%s` does not exist' % (permid)) + 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. - Each method needs to have USER as argument this is then based on given - API_KEY propagated as instance of user object + example function:: - Preferably this should be first argument also - + def func(apiuser,arg1, arg2,...): + pass Each function should also **raise** JSONRPCError for any - errors that happens + errors that happens. """ @HasPermissionAllDecorator('hg.admin') + def test(self, apiuser, args): + return args + + @HasPermissionAllDecorator('hg.admin') def pull(self, apiuser, repoid): """ - Dispatch pull action on given repo + 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:: - :param apiuser: - :param repoid: + id : + result : { + "msg": "Pulled from ``" + "repository": "" + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "Unable to pull changes from ``" + } + """ repo = get_repo_or_error(repoid) @@ -200,7 +197,10 @@ try: ScmModel().pull_changes(repo.repo_name, self.rhodecode_user.username) - return 'Pulled from `%s`' % repo.repo_name + return dict( + msg='Pulled from `%s`' % repo.repo_name, + repository=repo.repo_name + ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( @@ -210,12 +210,34 @@ @HasPermissionAllDecorator('hg.admin') def rescan_repos(self, apiuser, remove_obsolete=Optional(False)): """ - Dispatch rescan repositories action. If remove_obsolete is set + 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" + 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:: - :param apiuser: - :param remove_obsolete: + id : + result : { + 'added': [,...] + 'removed': [,...] + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + 'Error occurred during rescan repositories action' + } + """ try: @@ -231,22 +253,47 @@ def invalidate_cache(self, apiuser, repoid): """ - Dispatch cache invalidation action on given repo + 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:: - :param apiuser: - :param repoid: + id : + result : { + 'msg': Cache for repository `` was invalidated, + 'repository': + } + error : null + + ERROR OUTPUT:: + + id : + 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 HasRepoPermissionAnyApi('repository.admin', - 'repository.write')(user=apiuser, - repo_name=repo.repo_name) is False: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + 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 ('Caches of repository `%s` was invalidated' % repoid) + return dict( + msg='Cache for repository `%s` was invalidated' % (repoid,), + repository=repo.repo_name + ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( @@ -257,14 +304,47 @@ def lock(self, apiuser, repoid, locked=Optional(None), userid=Optional(OAttr('apiuser'))): """ - Set locking state on particular repository by given user, if - this command is runned by non-admin account userid is set to user - who is calling this method + 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:: - :param apiuser: - :param repoid: - :param userid: - :param locked: + id : + result : { + 'repo': '', + 'locked': , + 'locked_since': , + 'locked_by': , + 'lock_state_changed': , + 'msg': 'Repo `` locked by `` on .' + or + 'msg': 'Repo `` not locked.' + or + 'msg': 'User `` set lock state for repo `` to ``' + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + 'Error occurred locking repository `` + } + """ repo = get_repo_or_error(repoid) if HasPermissionAnyApi('hg.admin')(user=apiuser): @@ -272,14 +352,14 @@ 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 + # 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)) + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) if isinstance(userid, Optional): userid = apiuser.user_id @@ -295,6 +375,7 @@ 'locked': False, 'locked_since': None, 'locked_by': None, + 'lock_state_changed': False, 'msg': 'Repo `%s` not locked.' % repo.repo_name } return _d @@ -306,8 +387,9 @@ 'locked': True, 'locked_since': time_, 'locked_by': lock_user.username, - 'msg': ('Repo `%s` locked by `%s`. ' - % (repo.repo_name, + '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 @@ -327,6 +409,7 @@ 'locked': locked, 'locked_since': lock_time, 'locked_by': user.username, + 'lock_state_changed': True, 'msg': ('User `%s` set lock state for repo `%s` to `%s`' % (user.username, repo.repo_name, locked)) } @@ -339,28 +422,39 @@ def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))): """ - Get all locks for given userid, if + Get all repositories with locks for given userid, if this command is runned by non-admin account userid is set to user - who is calling this method, thus returning locks for himself + who is calling this method, thus returning locks for himself. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: User to get locks for + :type userid: Optional(str or int) - :param apiuser: - :param userid: + OUTPUT:: + + id : + result : { + [repo_object, repo_object,...] + } + error : null """ if not HasPermissionAnyApi('hg.admin')(user=apiuser): - #make sure normal user does not pass someone else userid, - #he is not allowed to do that + # 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' ) + ret = [] if isinstance(userid, Optional): user = None else: user = get_user_or_error(userid) - #show all locks + # show all locks for r in Repository.getAll(): userid, time_ = r.locked if time_: @@ -375,31 +469,114 @@ return ret @HasPermissionAllDecorator('hg.admin') - def show_ip(self, apiuser, userid): + def get_ip(self, apiuser, userid=Optional(OAttr('apiuser'))): """ Shows IP address as seen from RhodeCode server, together with all - defined IP addresses for given user + defined IP addresses for given user. If userid is not passed data is + returned for user who's calling this function. + 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 userid: username to show ips for + :type userid: Optional(str or int) + + OUTPUT:: - :param apiuser: - :param userid: + id : + result : { + "server_ip_addr": "", + "user_ips": [ + { + "ip_addr": "", + "ip_range": ["", ""], + }, + ... + ] + } + """ + if isinstance(userid, Optional): + userid = apiuser.user_id user = get_user_or_error(userid) ips = UserIpMap.query().filter(UserIpMap.user == user).all() return dict( - ip_addr_server=self.ip_addr, + server_ip_addr=self.ip_addr, user_ips=ips ) + # alias for old + show_ip = get_ip + + @HasPermissionAllDecorator('hg.admin') + def get_server_info(self, apiuser): + """ + return server info, including RhodeCode version and installed packages + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: + + id : + result : { + 'modules': [,...] + 'py_version': , + 'platform': , + 'rhodecode_version': + } + error : null + """ + return RhodeCodeSetting.get_server_info() + def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))): - """" - Get a user by username, or userid, if userid is given + """ + Get's an user by username or user_id, Returns empty result if user is + not found. If userid param is skipped it is set to id of user who is + calling this method. This command can be executed only using api_key + belonging to user with admin rights, or regular users that cannot + specify different userid than theirs + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: user to get data for + :type userid: Optional(str or int) + + OUTPUT:: - :param apiuser: - :param userid: + id : + result: None if user does not exist or + { + "user_id" : "", + "api_key" : "", + "api_keys": "[]" + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "emails": "[]", + "ip_addresses": "[,...]", + "active" : "", + "admin" :ย  "", + "extern_name" : "", + "extern_type" : " + "last_login": "", + "permissions": { + "global": ["hg.create.repository", + "repository.read", + "hg.register.manual_activate"], + "repositories": {"repo1": "repository.none"}, + "repositories_groups": {"Group1": "group.read"} + }, + } + + error: null + """ if not HasPermissionAnyApi('hg.admin')(user=apiuser): - #make sure normal user does not pass someone else userid, - #he is not allowed to do that + # 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' @@ -415,47 +592,91 @@ @HasPermissionAllDecorator('hg.admin') def get_users(self, apiuser): - """" - Get all users + """ + Lists all existing users. This command can be executed only using api_key + belonging to user with admin rights. - :param apiuser: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: + + id : + result: [, ...] + error: null """ result = [] - users_list = User.query().order_by(User.username)\ - .filter(User.username != User.DEFAULT_USER)\ - .all() + users_list = User.query().order_by(User.username) \ + .filter(User.username != User.DEFAULT_USER) \ + .all() for user in users_list: result.append(user.get_api_data()) return result @HasPermissionAllDecorator('hg.admin') - def create_user(self, apiuser, username, email, password=Optional(None), - firstname=Optional(None), lastname=Optional(None), + def create_user(self, apiuser, username, email, password=Optional(''), + firstname=Optional(''), lastname=Optional(''), active=Optional(True), admin=Optional(False), - ldap_dn=Optional(None)): + extern_name=Optional('rhodecode'), + extern_type=Optional('rhodecode')): """ - Create new user + Creates new user. Returns new user object. This command can + be executed only using api_key belonging to user with admin rights. - :param apiuser: - :param username: - :param email: - :param password: - :param firstname: - :param lastname: - :param active: - :param admin: - :param ldap_dn: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param username: new username + :type username: str or int + :param email: email + :type email: str + :param password: password + :type password: Optional(str) + :param firstname: firstname + :type firstname: Optional(str) + :param lastname: lastname + :type lastname: Optional(str) + :param active: active + :type active: Optional(bool) + :param admin: admin + :type admin: Optional(bool) + :param extern_name: name of extern + :type extern_name: Optional(str) + :param extern_type: extern_type + :type extern_type: Optional(str) + + + OUTPUT:: + + id : + result: { + "msg" : "created new user ``", + "user": + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "user `` already exist" + or + "email `` already exist" + or + "failed to create user ``" + } + """ if UserModel().get_by_username(username): - raise JSONRPCError("user `%s` already exist" % username) + raise JSONRPCError("user `%s` already exist" % (username,)) if UserModel().get_by_email(email, case_insensitive=True): - raise JSONRPCError("email `%s` already exist" % email) + raise JSONRPCError("email `%s` already exist" % (email,)) - if Optional.extract(ldap_dn): - # generate temporary password if ldap_dn + if Optional.extract(extern_name): + # generate temporary password if user is external password = PasswordGenerator().gen_password(length=8) try: @@ -467,7 +688,8 @@ lastname=Optional.extract(lastname), active=Optional.extract(active), admin=Optional.extract(admin), - ldap_dn=Optional.extract(ldap_dn) + extern_type=Optional.extract(extern_type), + extern_name=Optional.extract(extern_name) ) Session().commit() return dict( @@ -476,48 +698,77 @@ ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create user `%s`' % username) + raise JSONRPCError('failed to create user `%s`' % (username,)) @HasPermissionAllDecorator('hg.admin') def update_user(self, apiuser, userid, username=Optional(None), - email=Optional(None), firstname=Optional(None), - lastname=Optional(None), active=Optional(None), - admin=Optional(None), ldap_dn=Optional(None), - password=Optional(None)): + email=Optional(None),password=Optional(None), + firstname=Optional(None), lastname=Optional(None), + active=Optional(None), admin=Optional(None), + extern_type=Optional(None), extern_name=Optional(None),): """ - Updates given user + updates given user if such user exists. This command can + be executed only using api_key belonging to user with admin rights. - :param apiuser: - :param userid: - :param username: - :param email: - :param firstname: - :param lastname: - :param active: - :param admin: - :param ldap_dn: - :param password: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: userid to update + :type userid: str or int + :param username: new username + :type username: str or int + :param email: email + :type email: str + :param password: password + :type password: Optional(str) + :param firstname: firstname + :type firstname: Optional(str) + :param lastname: lastname + :type lastname: Optional(str) + :param active: active + :type active: Optional(bool) + :param admin: admin + :type admin: Optional(bool) + :param extern_name: + :type extern_name: Optional(str) + :param extern_type: + :type extern_type: Optional(str) + + + OUTPUT:: + + id : + result: { + "msg" : "updated user ID: ", + "user": , + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to update user ``" + } + """ user = get_user_or_error(userid) - # call function and store only updated arguments + # only non optional arguments will be stored in updates updates = {} - def store_update(attr, name): - if not isinstance(attr, Optional): - updates[name] = attr - try: - store_update(username, 'username') - store_update(password, 'password') - store_update(email, 'email') - store_update(firstname, 'name') - store_update(lastname, 'lastname') - store_update(active, 'active') - store_update(admin, 'admin') - store_update(ldap_dn, 'ldap_dn') + store_update(updates, username, 'username') + store_update(updates, password, 'password') + store_update(updates, email, 'email') + store_update(updates, firstname, 'name') + store_update(updates, lastname, 'lastname') + store_update(updates, active, 'active') + store_update(updates, admin, 'admin') + store_update(updates, extern_name, 'extern_name') + store_update(updates, extern_type, 'extern_type') user = UserModel().update_user(user, **updates) Session().commit() @@ -530,15 +781,36 @@ raise JSONRPCError('editing default user is forbidden') except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to update user `%s`' % userid) + raise JSONRPCError('failed to update user `%s`' % (userid,)) @HasPermissionAllDecorator('hg.admin') def delete_user(self, apiuser, userid): - """" - Deletes an user + """ + deletes givenuser if such user exists. 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 userid: user to delete + :type userid: str or int + + OUTPUT:: - :param apiuser: - :param userid: + id : + result: { + "msg" : "deleted user ID: ", + "user": null + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete user ID: " + } + """ user = get_user_or_error(userid) @@ -550,57 +822,114 @@ user=None ) except Exception: + log.error(traceback.format_exc()) - raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id, - user.username)) + raise JSONRPCError('failed to delete user ID:%s %s' + % (user.user_id, user.username)) - @HasPermissionAllDecorator('hg.admin') - def get_users_group(self, apiuser, usersgroupid): - """" - Get user group by name or id + # permission check inside + def get_user_group(self, apiuser, usergroupid): + """ + Gets an existing user group. This command can be executed only using api_key + belonging to user with admin rights or user who has at least + read access to user group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: id of user_group to edit + :type usergroupid: str or int + + OUTPUT:: - :param apiuser: - :param usersgroupid: - """ - users_group = get_users_group_or_error(usersgroupid) + id : + result : None if group not exist + { + "users_group_id" : "", + "group_name" : "", + "active": "", + "members" : [,...] + } + error : null - data = users_group.get_api_data() + """ + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) - members = [] - for user in users_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members + data = user_group.get_api_data() return data - @HasPermissionAllDecorator('hg.admin') - def get_users_groups(self, apiuser): - """" - Get all user groups + # permission check inside + def get_user_groups(self, apiuser): + """ + Lists all existing user groups. This command can be executed only using + api_key belonging to user with admin rights or user who has at least + read access to user group. - :param apiuser: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: + + id : + result : [,...] + error : null """ result = [] - for users_group in UserGroupModel().get_all(): - result.append(users_group.get_api_data()) + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + extras = {'user': apiuser} + for user_group in UserGroupList(UserGroupModel().get_all(), + perm_set=_perms, extra_kwargs=extras): + result.append(user_group.get_api_data()) return result - @HasPermissionAllDecorator('hg.admin') - def create_users_group(self, apiuser, group_name, - owner=Optional(OAttr('apiuser')), - active=Optional(True)): + @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') + def create_user_group(self, apiuser, group_name, description=Optional(''), + owner=Optional(OAttr('apiuser')), active=Optional(True)): """ - Creates an new usergroup + Creates new user group. This command can be executed only using api_key + belonging to user with admin rights or an user who has create user group + permission + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param group_name: name of new user group + :type group_name: str + :param description: group description + :type description: str + :param owner: owner of group. If not passed apiuser is the owner + :type owner: Optional(str or int) + :param active: group is active + :type active: Optional(bool) + + OUTPUT:: - :param apiuser: - :param group_name: - :param owner: - :param active: + id : + result: { + "msg": "created new user group ``", + "user_group": + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "user group `` already exist" + or + "failed to create group ``" + } + """ if UserGroupModel().get_by_name(group_name): - raise JSONRPCError("user group `%s` already exist" % group_name) + raise JSONRPCError("user group `%s` already exist" % (group_name,)) try: if isinstance(owner, Optional): @@ -608,36 +937,191 @@ owner = get_user_or_error(owner) active = Optional.extract(active) - ug = UserGroupModel().create(name=group_name, - owner=owner, - active=active) + description = Optional.extract(description) + ug = UserGroupModel().create(name=group_name, description=description, + owner=owner, active=active) Session().commit() return dict( msg='created new user group `%s`' % group_name, - users_group=ug.get_api_data() + user_group=ug.get_api_data() + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to create group `%s`' % (group_name,)) + + # permission check inside + def update_user_group(self, apiuser, usergroupid, group_name=Optional(''), + description=Optional(''), owner=Optional(None), + active=Optional(True)): + """ + Updates given usergroup. This command can be executed only using api_key + belonging to user with admin rights or an admin of given user group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: id of user group to update + :type usergroupid: str or int + :param group_name: name of new user group + :type group_name: str + :param description: group description + :type description: str + :param owner: owner of group. + :type owner: Optional(str or int) + :param active: group is active + :type active: Optional(bool) + + OUTPUT:: + + id : + result : { + "msg": 'updated user group ID: ', + "user_group": + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to update user group ``" + } + + """ + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) + + if not isinstance(owner, Optional): + owner = get_user_or_error(owner) + + updates = {} + store_update(updates, group_name, 'users_group_name') + store_update(updates, description, 'user_group_description') + store_update(updates, owner, 'user') + store_update(updates, active, 'users_group_active') + try: + UserGroupModel().update(user_group, updates) + Session().commit() + return dict( + msg='updated user group ID:%s %s' % (user_group.users_group_id, + user_group.users_group_name), + user_group=user_group.get_api_data() ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create group `%s`' % group_name) + raise JSONRPCError('failed to update user group `%s`' % (usergroupid,)) + + # permission check inside + def delete_user_group(self, apiuser, usergroupid): + """ + Delete given user group by user group id or name. + This command can be executed only using api_key + belonging to user with admin rights or an admin of given user group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: + :type usergroupid: int + + OUTPUT:: + + id : + result : { + "msg": "deleted user group ID: " + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete user group ID: " + or + "RepoGroup assigned to " + } + + """ + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) - @HasPermissionAllDecorator('hg.admin') - def add_user_to_users_group(self, apiuser, usersgroupid, userid): - """" - Add a user to a user group + try: + UserGroupModel().delete(user_group) + Session().commit() + return dict( + msg='deleted user group ID:%s %s' % + (user_group.users_group_id, user_group.users_group_name), + user_group=None + ) + except UserGroupsAssignedException, e: + log.error(traceback.format_exc()) + raise JSONRPCError(str(e)) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to delete user group ID:%s %s' % + (user_group.users_group_id, + user_group.users_group_name) + ) + + # permission check inside + def add_user_to_user_group(self, apiuser, usergroupid, userid): + """ + Adds a user to a user group. If user exists in that group success will be + `false`. This command can be executed only using api_key + belonging to user with admin rights or an admin of given user group - :param apiuser: - :param usersgroupid: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: + :type usergroupid: int :param userid: + :type userid: int + + OUTPUT:: + + id : + result : { + "success": True|False # depends on if member is in group + "msg": "added member `` to user group `` | + User is already in that group" + + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to add member to user group ``" + } + """ user = get_user_or_error(userid) - users_group = get_users_group_or_error(usersgroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: - ugm = UserGroupModel().add_user_to_group(users_group, user) + ugm = UserGroupModel().add_user_to_group(user_group, user) success = True if ugm != True else False msg = 'added member `%s` to user group `%s`' % ( - user.username, users_group.users_group_name - ) + user.username, user_group.users_group_name + ) msg = msg if success else 'User is already in that group' Session().commit() @@ -649,28 +1133,48 @@ log.error(traceback.format_exc()) raise JSONRPCError( 'failed to add member to user group `%s`' % ( - users_group.users_group_name + user_group.users_group_name, ) ) - @HasPermissionAllDecorator('hg.admin') - def remove_user_from_users_group(self, apiuser, usersgroupid, userid): + # permission check inside + def remove_user_from_user_group(self, apiuser, usergroupid, userid): """ - Remove user from a group + Removes a user from a user group. If user is not in given group success will + be `false`. This command can be executed only + using api_key belonging to user with admin rights or an admin of given user group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: + :param userid: + - :param apiuser: - :param usersgroupid: - :param userid: + OUTPUT:: + + id : + result: { + "success": True|False, # depends on if member is in group + "msg": "removed member from user group | + User wasn't in group" + } + error: null + """ user = get_user_or_error(userid) - users_group = get_users_group_or_error(usersgroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: - success = UserGroupModel().remove_user_from_group(users_group, - user) + success = UserGroupModel().remove_user_from_group(user_group, user) msg = 'removed member `%s` from user group `%s`' % ( - user.username, users_group.users_group_name - ) + user.username, user_group.users_group_name + ) msg = msg if success else "User wasn't in group" Session().commit() return dict(success=success, msg=msg) @@ -678,42 +1182,99 @@ log.error(traceback.format_exc()) raise JSONRPCError( 'failed to remove member from user group `%s`' % ( - users_group.users_group_name - ) + user_group.users_group_name, + ) ) + # permission check inside def get_repo(self, apiuser, repoid): - """" - Get repository by name + """ + Gets an existing repository by it's name or repository_id. Members will return + either users_group or user associated to that repository. This command can be + executed only using api_key belonging to user with admin + rights or regular user that have at least read 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:: - :param apiuser: - :param repoid: + id : + result : { + { + "repo_id" : "", + "repo_name" : "" + "repo_type" : "", + "clone_uri" : "", + "enable_downloads": "", + "enable_locking": "", + "enable_statistics": "", + "private": "", + "created_on" : "", + "description" : "", + "landing_rev": "", + "last_changeset": { + "author": "", + "date": "", + "message": "", + "raw_id": "", + "revision": "", + "short_id": "" + } + "owner": "", + "fork_of": "", + "members" : [ + { + "name": "", + "type" : "user", + "permission" : "repository.(read|write|admin)" + }, + โ€ฆ + { + "name": "", + "type" : "user_group", + "permission" : "usergroup.(read|write|admin)" + }, + โ€ฆ + ] + "followers": [, ...] + ] + } + } + error : null + """ 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')(user=apiuser, - repo_name=repo.repo_name): - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + perms = ('repository.admin', 'repository.write', 'repository.read') + if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) members = [] followers = [] for user in repo.repo_to_perm: perm = user.permission.permission_name user = user.user - user_data = user.get_api_data() - user_data['type'] = "user" - user_data['permission'] = perm + user_data = { + 'name': user.username, + 'type': "user", + 'permission': perm + } members.append(user_data) - for users_group in repo.users_group_to_perm: - perm = users_group.permission.permission_name - users_group = users_group.users_group - users_group_data = users_group.get_api_data() - users_group_data['type'] = "users_group" - users_group_data['permission'] = perm - members.append(users_group_data) + for user_group in repo.users_group_to_perm: + perm = user_group.permission.permission_name + user_group = user_group.users_group + user_group_data = { + 'name': user_group.users_group_name, + 'type': "user_group", + 'permission': perm + } + members.append(user_group_data) for user in repo.followers: followers.append(user.user.get_api_data()) @@ -725,13 +1286,39 @@ # permission check inside def get_repos(self, apiuser): - """" - Get all repositories + """ + Lists all existing repositories. This command can be executed only using + api_key belonging to user with admin rights or regular user that have + admin, write or read access to repository. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: - :param apiuser: + id : + result: [ + { + "repo_id" : "", + "repo_name" : "" + "repo_type" : "", + "clone_uri" : "", + "private": : "", + "created_on" : "", + "description" : "", + "landing_rev": "", + "owner": "", + "fork_of": "", + "enable_downloads": "", + "enable_locking": "", + "enable_statistics": "", + }, + โ€ฆ + ] + error: null """ result = [] - if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: + if not HasPermissionAnyApi('hg.admin')(user=apiuser): repos = RepoModel().get_all_user_repos(user=apiuser) else: repos = RepoModel().get_all() @@ -740,21 +1327,49 @@ result.append(repo.get_api_data()) return result - @HasPermissionAllDecorator('hg.admin') + # permission check inside def get_repo_nodes(self, apiuser, repoid, revision, root_path, - ret_type='all'): + ret_type=Optional('all')): """ - returns a list of nodes and it's children - for a given path at given revision. It's possible to specify ret_type - to show only files or dirs + returns a list of nodes and it's children in a flat list for a given path + at given revision. It's possible to specify ret_type to show only `files` or + `dirs`. This command can be executed only using api_key belonging to + user with admin rights or regular user that have at least read access to repository. - :param apiuser: - :param repoid: name or id of repository + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int :param revision: revision for which listing should be done + :type revision: str :param root_path: path from which start displaying + :type root_path: str :param ret_type: return type 'all|files|dirs' nodes + :type ret_type: Optional(str) + + + OUTPUT:: + + id : + result: [ + { + "name" : "" + "type" : "", + }, + โ€ฆ + ] + error: null """ repo = get_repo_or_error(repoid) + + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo ! + perms = ('repository.admin', 'repository.write', 'repository.read') + if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) + + ret_type = Optional.extract(ret_type) + _map = {} try: _d, _f = ScmModel().get_nodes(repo, revision, root_path, flat=False) @@ -765,7 +1380,8 @@ } return _map[ret_type] except KeyError: - raise JSONRPCError('ret_type must be one of %s' % _map.keys()) + raise JSONRPCError('ret_type must be one of %s' + % (','.join(_map.keys()))) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( @@ -774,27 +1390,67 @@ @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')), - repo_type=Optional('hg'), - description=Optional(''), private=Optional(False), - clone_uri=Optional(None), landing_rev=Optional('tip'), + repo_type=Optional('hg'), description=Optional(''), + private=Optional(False), clone_uri=Optional(None), + landing_rev=Optional('rev:tip'), enable_statistics=Optional(False), enable_locking=Optional(False), - enable_downloads=Optional(False)): + enable_downloads=Optional(False), + copy_permissions=Optional(False)): """ - Create repository, if clone_url is given it makes a remote clone - if repo_name is within a group name the groups will be created - automatically if they aren't present + Creates a repository. If repository name contains "/", all needed repository + groups will be created. For example "foo/bar/baz" will create groups + "foo", "bar" (with "foo" as parent), and create "baz" repository with + "bar" as group. This command can be executed only using api_key + belonging to user with admin rights or regular user that have create + repository permission. Regular users cannot specify owner parameter - :param apiuser: - :param repo_name: - :param onwer: - :param repo_type: - :param description: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repo_name: repository name + :type repo_name: str + :param owner: user_id or username + :type owner: Optional(str) + :param repo_type: 'hg' or 'git' + :type repo_type: Optional(str) + :param description: repository description + :type description: Optional(str) :param private: + :type private: bool :param clone_uri: - :param landing_rev: + :type clone_uri: str + :param landing_rev: : + :type landing_rev: str + :param enable_locking: + :type enable_locking: bool + :param enable_downloads: + :type enable_downloads: bool + :param enable_statistics: + :type enable_statistics: bool + :param copy_permissions: Copy permission from group that repository is + beeing created. + :type copy_permissions: bool + + OUTPUT:: + + id : + result: { + "msg": "Created new repository ``", + "success": true, + "task": "" + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + 'failed to create repository `` + } + """ - if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: + if not HasPermissionAnyApi('hg.admin')(user=apiuser): if not isinstance(owner, Optional): #forbid setting owner for non-admins raise JSONRPCError( @@ -823,40 +1479,51 @@ clone_uri = Optional.extract(clone_uri) description = Optional.extract(description) landing_rev = Optional.extract(landing_rev) + copy_permissions = Optional.extract(copy_permissions) try: + repo_name_cleaned = repo_name.split('/')[-1] # create structure of groups and return the last group - group = map_groups(repo_name) - - repo = RepoModel().create_repo( - repo_name=repo_name, + repo_group = map_groups(repo_name) + data = dict( + repo_name=repo_name_cleaned, + repo_name_full=repo_name, repo_type=repo_type, - description=description, + repo_description=description, owner=owner, - private=private, + repo_private=private, clone_uri=clone_uri, - repos_group=group, - landing_rev=landing_rev, + repo_group=repo_group, + repo_landing_rev=landing_rev, enable_statistics=enable_statistics, + enable_locking=enable_locking, enable_downloads=enable_downloads, - enable_locking=enable_locking + repo_copy_permissions=copy_permissions, ) - Session().commit() + task = RepoModel().create(form_data=data, cur_user=owner) + from celery.result import BaseAsyncResult + task_id = None + if isinstance(task, BaseAsyncResult): + task_id = task.task_id + # no commit, it's done in RepoModel, or async via celery return dict( - msg="Created new repository `%s`" % (repo.repo_name), - repo=repo.get_api_data() + msg="Created new repository `%s`" % (repo_name,), + success=True, # cannot return the repo data here since fork + # cann be done async + task=task_id ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create repository `%s`' % repo_name) + raise JSONRPCError( + 'failed to create repository `%s`' % (repo_name,)) # permission check inside def update_repo(self, apiuser, repoid, name=Optional(None), owner=Optional(OAttr('apiuser')), group=Optional(None), description=Optional(''), private=Optional(False), - clone_uri=Optional(None), landing_rev=Optional('tip'), + clone_uri=Optional(None), landing_rev=Optional('rev:tip'), enable_statistics=Optional(False), enable_locking=Optional(False), enable_downloads=Optional(False)): @@ -917,9 +1584,53 @@ raise JSONRPCError('failed to update repo `%s`' % repoid) @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') - def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')), + def fork_repo(self, apiuser, repoid, fork_name, + owner=Optional(OAttr('apiuser')), description=Optional(''), copy_permissions=Optional(False), - private=Optional(False), landing_rev=Optional('tip')): + private=Optional(False), landing_rev=Optional('rev:tip')): + """ + Creates a fork of given repo. In case of using celery this will + immidiatelly return success message, while fork is going to be created + asynchronous. This command can be executed only using api_key belonging to + user with admin rights or regular user that have fork permission, and at least + read access to forking repository. Regular users cannot specify owner parameter. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param fork_name: + :param owner: + :param description: + :param copy_permissions: + :param private: + :param landing_rev: + + INPUT:: + + id : + api_key : "" + args: { + "repoid" : "", + "fork_name": "", + "owner": "", + "description": "", + "copy_permissions": "", + "private": "", + "landing_rev": "" + } + + OUTPUT:: + + id : + result: { + "msg": "Created fork of `` as ``", + "success": true, + "task": "" + } + error: null + + """ repo = get_repo_or_error(repoid) repo_name = repo.repo_name @@ -940,7 +1651,7 @@ 'Only RhodeCode admin can specify `owner` param' ) else: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) if isinstance(owner, Optional): owner = apiuser.user_id @@ -963,12 +1674,18 @@ update_after_clone=False, fork_parent_id=repo.repo_id, ) - RepoModel().create_fork(form_data, cur_user=owner) + task = RepoModel().create_fork(form_data, cur_user=owner) + # no commit, it's done in RepoModel, or async via celery + from celery.result import BaseAsyncResult + task_id = None + if isinstance(task, BaseAsyncResult): + task_id = task.task_id return dict( msg='Created fork of `%s` as `%s`' % (repo.repo_name, fork_name), - success=True # cannot return the repo data here since fork - # cann be done async + success=True, # cannot return the repo data here since fork + # cann be done async + task=task_id ) except Exception: log.error(traceback.format_exc()) @@ -977,22 +1694,38 @@ fork_name) ) - # perms handled inside - def delete_repo(self, apiuser, repoid, forks=Optional(None)): + # permission check inside + def delete_repo(self, apiuser, repoid, forks=Optional('')): """ - Deletes a given repository + Deletes a repository. This command can be executed only using api_key belonging + to user with admin rights or regular user that have admin access to repository. + When `forks` param is set it's possible to detach or delete forks of deleting + repository - :param apiuser: - :param repoid: - :param forks: detach or delete, what do do with attached forks for repo + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param forks: `detach` or `delete`, what do do with attached forks for repo + :type forks: Optional(str) + + OUTPUT:: + + id : + result: { + "msg": "Deleted repository ``", + "success": true + } + error: null + """ repo = get_repo_or_error(repoid) - if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: + if not HasPermissionAnyApi('hg.admin')(user=apiuser): # check if we have admin permission for this repo ! - if HasRepoPermissionAnyApi('repository.admin')(user=apiuser, - repo_name=repo.repo_name) is False: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser, + repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) try: handle_forks = Optional.extract(forks) @@ -1004,8 +1737,8 @@ _forks_msg = ' ' + 'Deleted %s forks' % len(_forks) elif _forks: raise JSONRPCError( - 'Cannot delete `%s` it still contains attached forks' - % repo.repo_name + 'Cannot delete `%s` it still contains attached forks' % + (repo.repo_name,) ) RepoModel().delete(repo, forks=forks) @@ -1017,18 +1750,32 @@ except Exception: log.error(traceback.format_exc()) raise JSONRPCError( - 'failed to delete repository `%s`' % repo.repo_name + 'failed to delete repository `%s`' % (repo.repo_name,) ) @HasPermissionAllDecorator('hg.admin') def grant_user_permission(self, apiuser, repoid, userid, perm): """ Grant permission for user on given repository, or update existing one - if found + if found. This command can be executed only using api_key belonging to user + with admin rights. - :param repoid: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int :param userid: - :param perm: + :param perm: (repository.(none|read|write|admin)) + :type perm: str + + OUTPUT:: + + id : + result: { + "msg" : "Granted perm: `` for user: `` in repo: ``", + "success": true + } + error: null """ repo = get_repo_or_error(repoid) user = get_user_or_error(userid) @@ -1056,19 +1803,30 @@ @HasPermissionAllDecorator('hg.admin') def revoke_user_permission(self, apiuser, repoid, userid): """ - Revoke permission for user on given repository + Revoke permission for user on given repository. 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 + :param userid: - :param apiuser: - :param repoid: - :param userid: + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm for user: `` in repo: ``", + "success": true + } + error: null + """ repo = get_repo_or_error(repoid) user = get_user_or_error(userid) try: - RepoModel().revoke_user_permission(repo=repo, user=user) - Session().commit() return dict( msg='Revoked perm for user: `%s` in repo: `%s`' % ( @@ -1084,33 +1842,123 @@ ) ) - @HasPermissionAllDecorator('hg.admin') - def grant_users_group_permission(self, apiuser, repoid, usersgroupid, - perm): + # permission check inside + def grant_user_group_permission(self, apiuser, repoid, usergroupid, perm): """ Grant permission for user group on given repository, or update - existing one if found + existing one if found. 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 + :param usergroupid: id of usergroup + :type usergroupid: str or int + :param perm: (repository.(none|read|write|admin)) + :type perm: str + + OUTPUT:: - :param apiuser: - :param repoid: - :param usersgroupid: - :param perm: + id : + result : { + "msg" : "Granted perm: `` for group: `` in repo: ``", + "success": true + + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user group: `` in repo ``' + } + """ repo = get_repo_or_error(repoid) perm = get_perm_or_error(perm) - users_group = get_users_group_or_error(usersgroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo ! + _perms = ('repository.admin',) + if not HasRepoPermissionAnyApi(*_perms)( + user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: - RepoModel().grant_users_group_permission(repo=repo, - group_name=users_group, - perm=perm) + RepoModel().grant_user_group_permission( + repo=repo, group_name=user_group, perm=perm) Session().commit() return dict( msg='Granted perm: `%s` for user group: `%s` in ' 'repo: `%s`' % ( - perm.permission_name, users_group.users_group_name, - repo.repo_name + perm.permission_name, user_group.users_group_name, + repo.repo_name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission for user group: `%s` in ' + 'repo: `%s`' % ( + usergroupid, repo.repo_name + ) + ) + + # permission check inside + def revoke_user_group_permission(self, apiuser, repoid, usergroupid): + """ + Revoke permission for user group on given repository. 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 + :param usergroupid: + + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm for group: `` in repo: ``", + "success": true + } + error: null + """ + repo = get_repo_or_error(repoid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo ! + _perms = ('repository.admin',) + if not HasRepoPermissionAnyApi(*_perms)( + user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) + + try: + RepoModel().revoke_user_group_permission( + repo=repo, group_name=user_group) + + Session().commit() + return dict( + msg='Revoked perm for user group: `%s` in repo: `%s`' % ( + user_group.users_group_name, repo.repo_name ), success=True ) @@ -1119,46 +1967,579 @@ raise JSONRPCError( 'failed to edit permission for user group: `%s` in ' 'repo: `%s`' % ( - usersgroupid, repo.repo_name + user_group.users_group_name, repo.repo_name ) ) @HasPermissionAllDecorator('hg.admin') - def revoke_users_group_permission(self, apiuser, repoid, usersgroupid): + def get_repo_group(self, apiuser, repogroupid): + """ + Returns given repo group together with permissions, and repositories + inside the group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: id/name of repository group + :type repogroupid: str or int + """ + repo_group = get_repo_group_or_error(repogroupid) + + members = [] + for user in repo_group.repo_group_to_perm: + perm = user.permission.permission_name + user = user.user + user_data = { + 'name': user.username, + 'type': "user", + 'permission': perm + } + members.append(user_data) + + for user_group in repo_group.users_group_to_perm: + perm = user_group.permission.permission_name + user_group = user_group.users_group + user_group_data = { + 'name': user_group.users_group_name, + 'type': "user_group", + 'permission': perm + } + members.append(user_group_data) + + data = repo_group.get_api_data() + data["members"] = members + return data + + @HasPermissionAllDecorator('hg.admin') + def get_repo_groups(self, apiuser): """ - Revoke permission for user group on given repository + Returns all repository groups + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + """ + result = [] + for repo_group in RepoGroupModel().get_all(): + result.append(repo_group.get_api_data()) + return result + + @HasPermissionAllDecorator('hg.admin') + def create_repo_group(self, apiuser, group_name, description=Optional(''), + owner=Optional(OAttr('apiuser')), + parent=Optional(None), + copy_permissions=Optional(False)): + """ + Creates a repository group. 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 group_name: + :type group_name: + :param description: + :type description: + :param owner: + :type owner: + :param parent: + :type parent: + :param copy_permissions: + :type copy_permissions: + + OUTPUT:: + + id : + result : { + "msg": "created new repo group ``" + "repo_group": + } + error : null + + ERROR OUTPUT:: - :param apiuser: - :param repoid: - :param usersgroupid: + id : + result : null + error : { + failed to create repo group `` + } + """ - repo = get_repo_or_error(repoid) - users_group = get_users_group_or_error(usersgroupid) + if RepoGroup.get_by_group_name(group_name): + raise JSONRPCError("repo group `%s` already exist" % (group_name,)) + + if isinstance(owner, Optional): + owner = apiuser.user_id + group_description = Optional.extract(description) + parent_group = Optional.extract(parent) + if not isinstance(parent, Optional): + parent_group = get_repo_group_or_error(parent_group) + + copy_permissions = Optional.extract(copy_permissions) + try: + repo_group = RepoGroupModel().create( + group_name=group_name, + group_description=group_description, + owner=owner, + parent=parent_group, + copy_permissions=copy_permissions + ) + Session().commit() + return dict( + msg='created new repo group `%s`' % group_name, + repo_group=repo_group.get_api_data() + ) + except Exception: + + log.error(traceback.format_exc()) + raise JSONRPCError('failed to create repo group `%s`' % (group_name,)) + + @HasPermissionAllDecorator('hg.admin') + def update_repo_group(self, apiuser, repogroupid, group_name=Optional(''), + description=Optional(''), + owner=Optional(OAttr('apiuser')), + parent=Optional(None), enable_locking=Optional(False)): + repo_group = get_repo_group_or_error(repogroupid) + + updates = {} + try: + store_update(updates, group_name, 'group_name') + store_update(updates, description, 'group_description') + store_update(updates, owner, 'owner') + store_update(updates, parent, 'parent_group') + store_update(updates, enable_locking, 'enable_locking') + repo_group = RepoGroupModel().update(repo_group, updates) + Session().commit() + return dict( + msg='updated repository group ID:%s %s' % (repo_group.group_id, + repo_group.group_name), + repo_group=repo_group.get_api_data() + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to update repository group `%s`' + % (repogroupid,)) + + @HasPermissionAllDecorator('hg.admin') + def delete_repo_group(self, apiuser, repogroupid): + """ + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + + OUTPUT:: + + id : + result : { + 'msg': 'deleted repo group ID: + 'repo_group': null + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete repo group ID: " + } + + """ + repo_group = get_repo_group_or_error(repogroupid) try: - RepoModel().revoke_users_group_permission(repo=repo, - group_name=users_group) - + RepoGroupModel().delete(repo_group) Session().commit() return dict( - msg='Revoked perm for user group: `%s` in repo: `%s`' % ( - users_group.users_group_name, repo.repo_name + msg='deleted repo group ID:%s %s' % + (repo_group.group_id, repo_group.group_name), + repo_group=None + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to delete repo group ID:%s %s' % + (repo_group.group_id, repo_group.group_name) + ) + + # permission check inside + def grant_user_permission_to_repo_group(self, apiuser, repogroupid, userid, + perm, apply_to_children=Optional('none')): + """ + Grant permission for user on given repository group, or update existing + one if found. This command can be executed only using api_key belonging + to user with admin rights, or user who has admin right to given repository + group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param userid: + :param perm: (group.(none|read|write|admin)) + :type perm: str + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result: { + "msg" : "Granted perm: `` (recursive:) for user: `` in repo group: ``", + "success": true + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user: `` in repo group: ``" + } + + """ + + repo_group = get_repo_group_or_error(repogroupid) + + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser, + group_name=repo_group.group_name): + raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,)) + + user = get_user_or_error(userid) + perm = get_perm_or_error(perm, prefix='group.') + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().add_permission(repo_group=repo_group, + obj=user, + obj_type="user", + perm=perm, + recursive=apply_to_children) + Session().commit() + return dict( + msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % ( + perm.permission_name, apply_to_children, user.username, repo_group.name ), success=True ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( + 'failed to edit permission for user: `%s` in repo group: `%s`' % ( + userid, repo_group.name)) + + # permission check inside + def revoke_user_permission_from_repo_group(self, apiuser, repogroupid, userid, + apply_to_children=Optional('none')): + """ + Revoke permission for user on given repository group. This command can + be executed only using api_key belonging to user with admin rights, or + user who has admin right to given repository group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param userid: + :type userid: + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm (recursive:) for user: `` in repo group: ``", + "success": true + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user: `` in repo group: ``" + } + + """ + + repo_group = get_repo_group_or_error(repogroupid) + + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser, + group_name=repo_group.group_name): + raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,)) + + user = get_user_or_error(userid) + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().delete_permission(repo_group=repo_group, + obj=user, + obj_type="user", + recursive=apply_to_children) + + Session().commit() + return dict( + msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % ( + apply_to_children, user.username, repo_group.name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission for user: `%s` in repo group: `%s`' % ( + userid, repo_group.name)) + + # permission check inside + def grant_user_group_permission_to_repo_group( + self, apiuser, repogroupid, usergroupid, perm, + apply_to_children=Optional('none'),): + """ + Grant permission for user group on given repository group, or update + existing one if found. This command can be executed only using + api_key belonging to user with admin rights, or user who has admin + right to given repository group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param usergroupid: id of usergroup + :type usergroupid: str or int + :param perm: (group.(none|read|write|admin)) + :type perm: str + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result : { + "msg" : "Granted perm: `` (recursive:) for user group: `` in repo group: ``", + "success": true + + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user group: `` in repo group: ``" + } + + """ + repo_group = get_repo_group_or_error(repogroupid) + perm = get_perm_or_error(perm, prefix='group.') + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + _perms = ('group.admin',) + if not HasRepoGroupPermissionAnyApi(*_perms)( + user=apiuser, group_name=repo_group.group_name): + raise JSONRPCError( + 'repository group `%s` does not exist' % (repogroupid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError( + 'user group `%s` does not exist' % (usergroupid,)) + + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().add_permission(repo_group=repo_group, + obj=user_group, + obj_type="user_group", + perm=perm, + recursive=apply_to_children) + Session().commit() + return dict( + msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % ( + perm.permission_name, apply_to_children, + user_group.users_group_name, repo_group.name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( 'failed to edit permission for user group: `%s` in ' - 'repo: `%s`' % ( - users_group.users_group_name, repo.repo_name + 'repo group: `%s`' % ( + usergroupid, repo_group.name ) ) + # permission check inside + def revoke_user_group_permission_from_repo_group( + self, apiuser, repogroupid, usergroupid, + apply_to_children=Optional('none')): + """ + Revoke permission for user group on given repository. This command can be + executed only using api_key belonging to user with admin rights, or + user who has admin right to given repository group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param usergroupid: + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm (recursive:) for user group: `` in repo group: ``", + "success": true + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user group: `` in repo group: ``" + } + + + """ + repo_group = get_repo_group_or_error(repogroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + _perms = ('group.admin',) + if not HasRepoGroupPermissionAnyApi(*_perms)( + user=apiuser, group_name=repo_group.group_name): + raise JSONRPCError( + 'repository group `%s` does not exist' % (repogroupid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError( + 'user group `%s` does not exist' % (usergroupid,)) + + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().delete_permission(repo_group=repo_group, + obj=user_group, + obj_type="user_group", + recursive=apply_to_children) + Session().commit() + return dict( + msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % ( + apply_to_children, user_group.users_group_name, repo_group.name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission for user group: `%s` in repo group: `%s`' % ( + user_group.users_group_name, repo_group.name + ) + ) + + def get_gist(self, apiuser, gistid): + """ + Get given gist by id + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param gistid: id of private or public gist + :type gistid: str + """ + gist = get_gist_or_error(gistid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if gist.gist_owner != apiuser.user_id: + raise JSONRPCError('gist `%s` does not exist' % (gistid,)) + return gist.get_api_data() + + def get_gists(self, apiuser, userid=Optional(OAttr('apiuser'))): + """ + Get all gists for given user. If userid is empty returned gists + are for user who called the api + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: user to get gists for + :type userid: Optional(str or int) + """ + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # 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' + ) + + if isinstance(userid, Optional): + user_id = apiuser.user_id + else: + user_id = get_user_or_error(userid).user_id + + gists = [] + _gists = Gist().query()\ + .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\ + .filter(Gist.gist_owner == user_id)\ + .order_by(Gist.created_on.desc()) + for gist in _gists: + gists.append(gist.get_api_data()) + return gists + def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')), gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1), description=Optional('')): + """ + Creates new Gist + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param files: files to be added to gist + {'filename': {'content':'...', 'lexer': null}, + 'filename2': {'content':'...', 'lexer': null}} + :type files: dict + :param owner: gist owner, defaults to api method caller + :type owner: Optional(str or int) + :param gist_type: type of gist 'public' or 'private' + :type gist_type: Optional(str) + :param lifetime: time in minutes of gist lifetime + :type lifetime: Optional(int) + :param description: gist description + :type description: Optional(str) + + OUTPUT:: + + id : + result : { + "msg": "created new gist", + "gist": {} + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to create gist" + } + + """ try: if isinstance(owner, Optional): owner = apiuser.user_id @@ -1168,10 +2549,6 @@ gist_type = Optional.extract(gist_type) lifetime = Optional.extract(lifetime) - # files: { - # 'filename': {'content':'...', 'lexer': null}, - # 'filename2': {'content':'...', 'lexer': null} - #} gist = GistModel().create(description=description, owner=owner, gist_mapping=files, @@ -1185,3 +2562,54 @@ except Exception: log.error(traceback.format_exc()) raise JSONRPCError('failed to create gist') + + # def update_gist(self, apiuser, gistid, files, owner=Optional(OAttr('apiuser')), + # gist_type=Optional(Gist.GIST_PUBLIC), + # gist_lifetime=Optional(-1), gist_description=Optional('')): + # gist = get_gist_or_error(gistid) + # updates = {} + + # permission check inside + def delete_gist(self, apiuser, gistid): + """ + Deletes existing gist + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param gistid: id of gist to delete + :type gistid: str + + OUTPUT:: + + id : + result : { + "deleted gist ID: ", + "gist": null + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete gist ID:" + } + + """ + gist = get_gist_or_error(gistid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if gist.gist_owner != apiuser.user_id: + raise JSONRPCError('gist `%s` does not exist' % (gistid,)) + + try: + GistModel().delete(gist) + Session().commit() + return dict( + msg='deleted gist ID:%s' % (gist.gist_access_id,), + gist=None + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to delete gist ID:%s' + % (gist.gist_access_id,)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/bookmarks.py --- a/rhodecode/controllers/bookmarks.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/bookmarks.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.bookmarks - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Bookmarks controller for rhodecode - - :created_on: Dec 1, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.bookmarks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Bookmarks controller for rhodecode + +:created_on: Dec 1, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging from pylons import tmpl_context as c diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/branches.py --- a/rhodecode/controllers/branches.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/branches.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.branches - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - branches controller for rhodecode - - :created_on: Apr 21, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.branches +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +branches controller for rhodecode + +:created_on: Apr 21, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import binascii diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/changelog.py --- a/rhodecode/controllers/changelog.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/changelog.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.changelog - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - changelog controller for rhodecode - - :created_on: Apr 21, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.changelog +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +changelog controller for rhodecode + +:created_on: Apr 21, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import traceback diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/changeset.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.changeset - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - changeset controller for pylons showoing changes beetween - revisions - - :created_on: Apr 25, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.changeset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +changeset controller for pylons showoing changes beetween +revisions + +:created_on: Apr 25, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback from collections import defaultdict @@ -161,7 +162,7 @@ if ig_ws: params[ig_ws_key] += [ig_ws_val] - lbl = _('%s line context') % ln_ctx + 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') @@ -173,9 +174,11 @@ 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.users_groups_array = repo_model.get_users_groups_js() + c.user_groups_array = repo_model.get_user_groups_js() def _index(self, revision, method): c.anchor_url = anchor_url @@ -199,9 +202,13 @@ if not c.cs_ranges: raise RepositoryError('Changeset range returned empty result') - except (RepositoryError, ChangesetDoesNotExistError, Exception), e: + except(ChangesetDoesNotExistError,), e: log.error(traceback.format_exc()) - h.flash(safe_str(e), category='error') + 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() @@ -303,6 +310,7 @@ 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: @@ -418,7 +426,8 @@ def delete_comment(self, repo_name, comment_id): co = ChangesetComment.get(comment_id) owner = co.author.user_id == c.rhodecode_user.user_id - if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: + repo_admin = h.HasRepoPermissionAny('repository.admin') + if h.HasPermissionAny('hg.admin')() or repo_admin or owner: ChangesetCommentsModel().delete(comment=co) Session().commit() return True @@ -437,3 +446,31 @@ return EmptyChangeset(message=str(e)) else: raise HTTPBadRequest() + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + @jsonify + def changeset_children(self, repo_name, revision): + if request.is_xhr: + changeset = c.rhodecode_repo.get_changeset(revision) + result = {"results": []} + if changeset.children: + result = {"results": changeset.children} + return result + else: + raise HTTPBadRequest() + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + @jsonify + def changeset_parents(self, repo_name, revision): + if request.is_xhr: + changeset = c.rhodecode_repo.get_changeset(revision) + result = {"results": []} + if changeset.parents: + result = {"results": changeset.parents} + return result + else: + raise HTTPBadRequest() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/compare.py --- a/rhodecode/controllers/compare.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/compare.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.compare - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - compare controller for pylons showing differences between two - repos, branches, bookmarks or tips - - :created_on: May 6, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.compare +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +compare controller for pylons showing differences between two +repos, branches, bookmarks or tips + +:created_on: May 6, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback @@ -35,12 +36,11 @@ from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError from rhodecode.lib.vcs.utils import safe_str -from rhodecode.lib.vcs.utils.hgcompat import scmutil, unionrepo +from rhodecode.lib.vcs.utils.hgcompat import unionrepo from rhodecode.lib import helpers as h from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib import diffs -from rhodecode.lib.utils2 import safe_str from rhodecode.model.db import Repository from rhodecode.lib.diffs import LimitedDiffContainer @@ -54,16 +54,17 @@ super(CompareController, self).__before__() def __get_rev_or_redirect(self, ref, repo, redirect_after=True, - partial=False): + 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 + :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 @@ -97,12 +98,17 @@ 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@org_rev - to other_repo@other_rev ... and the ancestor that would be used for 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: @@ -122,10 +128,12 @@ 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) + 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) + 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() @@ -138,22 +146,56 @@ elif alias == 'git': if org_repo != other_repo: - raise Exception('Comparing of different GIT repositories is not' - 'allowed. Got %s != %s' % (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) - so, se = org_repo.run_git_command( - 'log --reverse --pretty="format: %%H" -s -p %s..%s' - % (org_rev, other_rev) - ) - changesets = [org_repo.get_changeset(cs) - for cs in re.findall(r'[0-9a-fA-F]{40}', so)] + 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, org_ref_type, org_ref, other_ref_type, other_ref): + def index(self, repo_name): + c.compare_home = True + org_repo = c.rhodecode_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.rhodecode_db_repo.repo_name org_ref = (org_ref_type, org_ref) @@ -187,23 +229,27 @@ other_repo = Repository.get_by_repo_name(other_repo) if org_repo is None: - log.error('Could not find org repo %s' % org_repo) - raise HTTPNotFound + 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: - log.error('Could not find other repo %s' % other_repo) - raise HTTPNotFound - - if org_repo != other_repo and h.is_git(org_repo): - log.error('compare of two remote repos not available for GIT REPOS') - raise HTTPNotFound + 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: - log.error('compare of two different kind of remote repos not available') - raise HTTPNotFound + 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] @@ -211,19 +257,17 @@ 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.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.rhodecode_db_repo.statuses( + [x.raw_id for x in c.cs_ranges]) - c.statuses = c.rhodecode_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, @@ -238,8 +282,8 @@ 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() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/error.py --- a/rhodecode/controllers/error.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/error.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.error - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - RhodeCode error controller - - :created_on: Dec 8, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.error +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +RhodeCode error controller + +:created_on: Dec 8, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os import cgi import logging diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/feed.py --- a/rhodecode/controllers/feed.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/feed.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.feed - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Feed controller for rhodecode - - :created_on: Apr 23, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.feed +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Feed controller for rhodecode + +:created_on: Apr 23, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/files.py --- a/rhodecode/controllers/files.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/files.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.files - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Files controller for RhodeCode - - :created_on: Apr 21, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.files +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Files controller for RhodeCode + +:created_on: Apr 21, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os import logging @@ -85,13 +86,17 @@ return None url_ = url('files_add_home', repo_name=c.repo_name, - revision=0, f_path='') - add_new = h.link_to(_('Click here to add new file'), url_) - h.flash(h.literal(_('There are no files yet %s') % add_new), + revision=0, f_path='', anchor='edit') + add_new = h.link_to(_('Click here to add new file'), url_, class_="alert-link") + h.flash(h.literal(_('There are no files yet. %s') % add_new), category='warning') redirect(h.url('summary_home', repo_name=repo_name)) - - except RepositoryError, e: # including ChangesetDoesNotExistError + except(ChangesetDoesNotExistError, LookupError), e: + log.error(traceback.format_exc()) + msg = _('Such revision does not exist for this repository') + h.flash(msg, category='error') + raise HTTPNotFound() + except RepositoryError, e: h.flash(safe_str(e), category='error') raise HTTPNotFound() @@ -109,6 +114,11 @@ file_node = cs.get_node(path) if file_node.is_dir(): raise RepositoryError('given path is a directory') + 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 RepositoryError, e: h.flash(safe_str(e), category='error') raise HTTPNotFound() @@ -186,22 +196,40 @@ @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') - def history(self, repo_name, revision, f_path, annotate=False): - if request.environ.get('HTTP_X_PARTIAL_XHR'): - c.changeset = self.__get_cs_or_redirect(revision, repo_name) - c.f_path = f_path - c.annotate = annotate - c.file = c.changeset.get_node(f_path) - if c.file.is_file(): - file_last_cs = c.file.last_changeset - c.file_changeset = (c.changeset - if c.changeset.revision < file_last_cs.revision - else file_last_cs) - c.file_history, _hist = self._get_node_history(c.changeset, f_path) - c.authors = [] - for a in set([x.author for x in _hist]): - c.authors.append((h.email(a), h.person(a))) - return render('files/files_history_box.html') + @jsonify + def history(self, repo_name, revision, f_path): + changeset = self.__get_cs_or_redirect(revision, repo_name) + f_path = f_path + _file = changeset.get_node(f_path) + if _file.is_file(): + file_history, _hist = self._get_node_history(changeset, f_path) + + res = [] + for obj in file_history: + res.append({ + 'text': obj[1], + 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]] + }) + + data = { + 'more': False, + 'results': res + } + return data + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + def authors(self, repo_name, revision, f_path): + changeset = self.__get_cs_or_redirect(revision, repo_name) + f_path = f_path + _file = changeset.get_node(f_path) + if _file.is_file(): + file_history, _hist = self._get_node_history(changeset, f_path) + c.authors = [] + for a in set([x.author for x in _hist]): + c.authors.append((h.email(a), h.person(a))) + return render('files/files_history_box.html') @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', @@ -265,6 +293,66 @@ @LoginRequired() @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') + def delete(self, repo_name, revision, f_path): + repo = c.rhodecode_db_repo + if repo.enable_locking and repo.locked[0]: + h.flash(_('This repository is has been locked by %s on %s') + % (h.person_by_id(repo.locked[0]), + h.fmt_date(h.time_to_datetime(repo.locked[1]))), + 'warning') + return redirect(h.url('files_home', + repo_name=repo_name, revision='tip')) + + # check if revision is a branch identifier- basically we cannot + # create multiple heads via file editing + _branches = repo.scm_instance.branches + # check if revision is a branch name or branch hash + if revision not in _branches.keys() + _branches.values(): + h.flash(_('You can only delete files with revision ' + 'being a valid branch '), category='warning') + return redirect(h.url('files_home', + repo_name=repo_name, revision='tip', + f_path=f_path)) + + r_post = request.POST + + c.cs = self.__get_cs_or_redirect(revision, repo_name) + c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path) + + c.default_message = _('Deleted file %s via RhodeCode') % (f_path) + c.f_path = f_path + node_path = f_path + author = self.rhodecode_user.full_contact + + if r_post: + message = r_post.get('message') or c.default_message + + try: + nodes = { + node_path: { + 'content': '' + } + } + self.scm_model.delete_nodes( + user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo, + message=message, + nodes=nodes, + parent_cs=c.cs, + author=author, + ) + + h.flash(_('Successfully deleted file %s') % f_path, + category='success') + except Exception: + log.error(traceback.format_exc()) + h.flash(_('Error occurred during commit'), category='error') + return redirect(url('changeset_home', + repo_name=c.repo_name, revision='tip')) + + return render('files/files_delete.html') + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') def edit(self, repo_name, revision, f_path): repo = c.rhodecode_db_repo if repo.enable_locking and repo.locked[0]: @@ -468,19 +556,21 @@ log.debug('Archive %s is not yet cached' % (archive_name)) if not use_cached_archive: - #generate new archive + # generate new archive + temp_stream = None try: fd, archive = tempfile.mkstemp() - t = open(archive, 'wb') + temp_stream = open(archive, 'wb') log.debug('Creating new temp archive in %s' % archive) - cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos) - if archive_cache_enabled: + cs.fill_archive(stream=temp_stream, kind=fileformat, subrepos=subrepos) + if not subrepos and archive_cache_enabled: #if we generated the archive and use cache rename that log.debug('Storing new archive in %s' % cached_archive_path) shutil.move(archive, cached_archive_path) archive = cached_archive_path finally: - t.close() + if temp_stream: + temp_stream.close() def get_chunked_archive(archive): stream = open(archive, 'rb') @@ -654,28 +744,6 @@ log.error(traceback.format_exc()) return redirect(url('files_home', repo_name=c.repo_name, f_path=f_path)) - if node2.is_binary: - node2_content = 'binary file' - else: - node2_content = node2.content - - if node1.is_binary: - node1_content = 'binary file' - else: - node1_content = node1.content - - html_escape_table = { - "&": "\u0026", - '"': "\u0022", - "'": "\u0027", - ">": "\u003e", - "<": "\u003c", - '\\': "\u005c", - '\n': '\\n' - } - - c.orig1 = h.html_escape((node1_content), html_escape_table) - c.orig2 = h.html_escape((node2_content), html_escape_table) c.node1 = node1 c.node2 = node2 c.cs1 = c.changeset_1 diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/followers.py --- a/rhodecode/controllers/followers.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/followers.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.followers - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Followers controller for rhodecode - - :created_on: Apr 23, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.followers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Followers controller for rhodecode + +:created_on: Apr 23, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging from pylons import tmpl_context as c, request diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/forks.py --- a/rhodecode/controllers/forks.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/forks.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.forks - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - forks controller for rhodecode - - :created_on: Apr 23, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.forks +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +forks controller for rhodecode + +:created_on: Apr 23, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import formencode import traceback @@ -30,13 +31,13 @@ from pylons import tmpl_context as c, request, url from pylons.controllers.util import redirect from pylons.i18n.translation import _ +from webob.exc import HTTPNotFound, HTTPInternalServerError import rhodecode.lib.helpers as h from rhodecode.lib.helpers import Page from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ - NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\ - HasPermissionAnyDecorator + NotAnonymous, HasRepoPermissionAny, HasPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User,\ RhodeCodeUi @@ -44,6 +45,7 @@ from rhodecode.model.forms import RepoForkForm from rhodecode.model.scm import ScmModel, RepoGroupList from rhodecode.lib.utils2 import safe_int +from rhodecode.lib.utils import jsonify log = logging.getLogger(__name__) @@ -160,6 +162,7 @@ repo_groups=c.repo_groups_choices, landing_revs=c.landing_revs_choices)() form_result = {} + task_id = None try: form_result = _form.to_python(dict(request.POST)) @@ -169,16 +172,12 @@ # create fork is done sometimes async on celery, db transaction # management is handled there. - RepoModel().create_fork(form_result, self.rhodecode_user.user_id) - fork_url = h.link_to(form_result['repo_name_full'], - h.url('summary_home', repo_name=form_result['repo_name_full'])) - - h.flash(h.literal(_('Forked repository %s as %s') \ - % (repo_name, fork_url)), - category='success') + task = RepoModel().create_fork(form_result, self.rhodecode_user.user_id) + from celery.result import BaseAsyncResult + if isinstance(task, BaseAsyncResult): + task_id = task.task_id except formencode.Invalid, errors: c.new_repo = errors.value['repo_name'] - return htmlfill.render( render('forks/fork.html'), defaults=errors.value, @@ -190,4 +189,6 @@ h.flash(_('An error occurred during repository forking %s') % repo_name, category='error') - return redirect(h.url('summary_home', repo_name=repo_name)) + return redirect(h.url('repo_creating_home', + repo_name=form_result['repo_name_full'], + task_id=task_id)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/home.py --- a/rhodecode/controllers/home.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/home.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.home - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Home controller for Rhodecode - - :created_on: Feb 18, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.home +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Home controller for Rhodecode + +:created_on: Feb 18, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" import logging @@ -32,10 +33,11 @@ import rhodecode from rhodecode.lib import helpers as h +from rhodecode.lib.utils import jsonify, conditional_cache from rhodecode.lib.compat import json -from rhodecode.lib.auth import LoginRequired +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import Repository +from rhodecode.model.db import Repository, RepoGroup from rhodecode.model.repo import RepoModel @@ -49,7 +51,7 @@ @LoginRequired() def index(self): - c.groups = self.scm_model.get_repos_groups() + c.groups = self.scm_model.get_repo_groups() c.group = None c.repos_list = Repository.query()\ @@ -65,21 +67,80 @@ return render('/index.html') @LoginRequired() - def repo_switcher(self): - if request.is_xhr: + @jsonify + def repo_switcher_data(self): + #wrapper for conditional cache + def _c(): + log.debug('generating switcher repo/groups list') all_repos = Repository.query().order_by(Repository.repo_name).all() - c.repos_list = self.scm_model.get_repos(all_repos, - sort_key='name_sort', - simple=True) - return render('/repo_switcher_list.html') + repo_iter = self.scm_model.get_repos(all_repos, simple=True) + all_groups = RepoGroup.query().order_by(RepoGroup.group_name).all() + repo_groups_iter = self.scm_model.get_repo_groups(all_groups) + + res = [{ + 'text': _('Groups'), + 'children': [ + {'id': obj.group_name, 'text': obj.group_name, + 'type': 'group', 'obj': {}} for obj in repo_groups_iter] + }, { + 'text': _('Repositories'), + 'children': [ + {'id': obj['name'], 'text': obj['name'], + 'type': 'repo', 'obj': obj['dbrepo']} for obj in repo_iter] + }] + + data = { + 'more': False, + 'results': res + } + return data + + if request.is_xhr: + condition = False + compute = conditional_cache('short_term', 'cache_desc', + condition=condition, func=_c) + return compute() else: raise HTTPBadRequest() @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') def branch_tag_switcher(self, repo_name): if request.is_xhr: - c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) + c.rhodecode_db_repo = Repository.get_by_repo_name(repo_name) if c.rhodecode_db_repo: c.rhodecode_repo = c.rhodecode_db_repo.scm_instance return render('/switch_to_list.html') raise HTTPBadRequest() + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + @jsonify + def repo_refs_data(self, repo_name): + repo = Repository.get_by_repo_name(repo_name).scm_instance + res = [] + _branches = repo.branches.items() + if _branches: + res.append({ + 'text': _('Branch'), + 'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches] + }) + _tags = repo.tags.items() + if _tags: + res.append({ + 'text': _('Tag'), + 'children': [{'id': rev, 'text': name, 'type': 'tag'} for name, rev in _tags] + }) + _bookmarks = repo.bookmarks.items() + if _bookmarks: + res.append({ + 'text': _('Bookmark'), + 'children': [{'id': rev, 'text': name, 'type': 'book'} for name, rev in _bookmarks] + }) + data = { + 'more': False, + 'results': res + } + return data diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/journal.py --- a/rhodecode/controllers/journal.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/journal.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.journal - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Journal controller for pylons - - :created_on: Nov 21, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.journal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Journal controller for pylons + +:created_on: Nov 21, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + import logging from itertools import groupby @@ -230,8 +232,8 @@ def quick_menu(repo_name): return _render('quick_menu', repo_name) - def repo_lnk(name, rtype, private, fork_of): - return _render('repo_name', name, rtype, private, fork_of, + def repo_lnk(name, rtype, rstate, private, fork_of): + return _render('repo_name', name, rtype, rstate, private, fork_of, short_name=False, admin=False) def last_rev(repo_name, cs_cache): @@ -262,9 +264,9 @@ "menu": quick_menu(repo.repo_name), "raw_name": repo.repo_name.lower(), "name": repo_lnk(repo.repo_name, repo.repo_type, - repo.private, repo.fork), + repo.repo_state, repo.private, repo.fork), "last_changeset": last_rev(repo.repo_name, cs_cache), - "raw_tip": cs_cache.get('revision'), + "last_rev_raw": cs_cache.get('revision'), "action": toogle_follow(repo.repo_id) } diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/login.py --- a/rhodecode/controllers/login.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/login.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.login - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Login controller for rhodeocode - - :created_on: Apr 22, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.login +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Login controller for rhodeocode + +:created_on: Apr 22, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import formencode @@ -31,14 +32,15 @@ from formencode import htmlfill from webob.exc import HTTPFound from pylons.i18n.translation import _ -from pylons.controllers.util import abort, redirect -from pylons import request, response, session, tmpl_context as c, url +from pylons.controllers.util import redirect +from pylons import request, session, tmpl_context as c, url import rhodecode.lib.helpers as h from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator +from rhodecode.lib.auth_modules import importplugin from rhodecode.lib.base import BaseController, render from rhodecode.lib.exceptions import UserCreationError -from rhodecode.model.db import User +from rhodecode.model.db import User, RhodeCodeSetting from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm from rhodecode.model.user import UserModel from rhodecode.model.meta import Session @@ -52,13 +54,63 @@ def __before__(self): super(LoginController, self).__before__() + def _store_user_in_session(self, username, remember=False): + user = User.get_by_username(username, case_insensitive=True) + auth_user = AuthUser(user.user_id) + auth_user.set_authenticated() + cs = auth_user.get_cookie_store() + session['rhodecode_user'] = cs + user.update_lastlogin() + Session().commit() + + # If they want to be remembered, update the cookie + if remember: + _year = (datetime.datetime.now() + + datetime.timedelta(seconds=60 * 60 * 24 * 365)) + session._set_cookie_expires(_year) + + session.save() + + log.info('user %s is now authenticated and stored in ' + 'session, session attrs %s' % (username, cs)) + + # dumps session attrs back to cookie + session._update_cookie_out() + # we set new cookie + headers = None + if session.request['set_cookie']: + # send set-cookie headers back to response to update cookie + headers = [('Set-Cookie', session.request['cookie_out'])] + return headers + + def _validate_came_from(self, came_from): + if not came_from: + return came_from + + parsed = urlparse.urlparse(came_from) + server_parsed = urlparse.urlparse(url.current()) + allowed_schemes = ['http', 'https'] + if parsed.scheme and parsed.scheme not in allowed_schemes: + log.error('Suspicious URL scheme detected %s for url %s' % + (parsed.scheme, parsed)) + came_from = url('home') + elif server_parsed.netloc != parsed.netloc: + log.error('Suspicious NETLOC detected %s for url %s server url ' + 'is: %s' % (parsed.netloc, parsed, server_parsed)) + came_from = url('home') + return came_from + def index(self): + _default_came_from = url('home') + came_from = self._validate_came_from(request.GET.get('came_from')) + c.came_from = came_from or _default_came_from + + not_default = self.rhodecode_user.username != User.DEFAULT_USER + ip_allowed = self.rhodecode_user.ip_allowed + # redirect if already logged in - c.came_from = request.GET.get('came_from') - not_default = self.rhodecode_user.username != 'default' - ip_allowed = self.rhodecode_user.ip_allowed if self.rhodecode_user.is_authenticated and not_default and ip_allowed: - return redirect(url('home')) + raise HTTPFound(location=c.came_from) if request.POST: # import Login Form validator class @@ -67,53 +119,10 @@ session.invalidate() c.form_result = login_form.to_python(dict(request.POST)) # form checks for username/password, now we're authenticated - username = c.form_result['username'] - user = User.get_by_username(username, case_insensitive=True) - auth_user = AuthUser(user.user_id) - auth_user.set_authenticated() - cs = auth_user.get_cookie_store() - session['rhodecode_user'] = cs - user.update_lastlogin() - Session().commit() - - # If they want to be remembered, update the cookie - if c.form_result['remember']: - _year = (datetime.datetime.now() + - datetime.timedelta(seconds=60 * 60 * 24 * 365)) - session._set_cookie_expires(_year) - - session.save() - - log.info('user %s is now authenticated and stored in ' - 'session, session attrs %s' % (username, cs)) - - # dumps session attrs back to cookie - session._update_cookie_out() - - # we set new cookie - headers = None - if session.request['set_cookie']: - # send set-cookie headers back to response to update cookie - headers = [('Set-Cookie', session.request['cookie_out'])] - - allowed_schemes = ['http', 'https'] - if c.came_from: - parsed = urlparse.urlparse(c.came_from) - server_parsed = urlparse.urlparse(url.current()) - if parsed.scheme and parsed.scheme not in allowed_schemes: - log.error( - 'Suspicious URL scheme detected %s for url %s' % - (parsed.scheme, parsed)) - c.came_from = url('home') - elif server_parsed.netloc != parsed.netloc: - log.error('Suspicious NETLOC detected %s for url %s' - 'server url is: %s' % - (parsed.netloc, parsed, server_parsed)) - c.came_from = url('home') - raise HTTPFound(location=c.came_from, headers=headers) - else: - raise HTTPFound(location=url('home'), headers=headers) - + headers = self._store_user_in_session( + username=c.form_result['username'], + remember=c.form_result['remember']) + raise HTTPFound(location=c.came_from, headers=headers) except formencode.Invalid, errors: defaults = errors.value # remove password from filling in form again @@ -131,6 +140,21 @@ # Exception itself h.flash(e, 'error') + # check if we use container plugin, and try to login using it. + auth_plugins = RhodeCodeSetting.get_auth_plugins() + if any((importplugin(name).is_container_auth for name in auth_plugins)): + from rhodecode.lib import auth_modules + try: + auth_info = auth_modules.authenticate('', '', request.environ) + except UserCreationError, e: + log.error(e) + h.flash(e, 'error') + # render login, with flash message about limit + return render('/login.html') + + if auth_info: + headers = self._store_user_in_session(auth_info.get('username')) + raise HTTPFound(location=c.came_from, headers=headers) return render('/login.html') @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate', @@ -139,14 +163,33 @@ c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\ .AuthUser.permissions['global'] + settings = RhodeCodeSetting.get_app_settings() + captcha_private_key = settings.get('rhodecode_captcha_private_key') + c.captcha_active = bool(captcha_private_key) + c.captcha_public_key = settings.get('rhodecode_captcha_public_key') + if request.POST: register_form = RegisterForm()() try: form_result = register_form.to_python(dict(request.POST)) form_result['active'] = c.auto_active + + if c.captcha_active: + from rhodecode.lib.recaptcha import submit + response = submit(request.POST.get('recaptcha_challenge_field'), + request.POST.get('recaptcha_response_field'), + private_key=captcha_private_key, + remoteip=self.ip_addr) + if c.captcha_active and not response.is_valid: + _value = form_result + _msg = _('bad captcha') + error_dict = {'recaptcha_field': _msg} + raise formencode.Invalid(_msg, _value, None, + error_dict=error_dict) + UserModel().create_registration(form_result) h.flash(_('You have successfully registered into RhodeCode'), - category='success') + category='success') Session().commit() return redirect(url('login_home')) @@ -167,10 +210,27 @@ return render('/register.html') def password_reset(self): + settings = RhodeCodeSetting.get_app_settings() + captcha_private_key = settings.get('rhodecode_captcha_private_key') + c.captcha_active = bool(captcha_private_key) + c.captcha_public_key = settings.get('rhodecode_captcha_public_key') + if request.POST: password_reset_form = PasswordResetForm()() try: form_result = password_reset_form.to_python(dict(request.POST)) + if c.captcha_active: + from rhodecode.lib.recaptcha import submit + response = submit(request.POST.get('recaptcha_challenge_field'), + request.POST.get('recaptcha_response_field'), + private_key=captcha_private_key, + remoteip=self.ip_addr) + if c.captcha_active and not response.is_valid: + _value = form_result + _msg = _('bad captcha') + error_dict = {'recaptcha_field': _msg} + raise formencode.Invalid(_msg, _value, None, + error_dict=error_dict) UserModel().reset_password_link(form_result) h.flash(_('Your password reset link was sent'), category='success') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/pullrequests.py --- a/rhodecode/controllers/pullrequests.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/pullrequests.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.pullrequests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - pull requests controller for rhodecode for initializing pull requests - - :created_on: May 7, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.pullrequests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +pull requests controller for rhodecode for initializing pull requests + +:created_on: May 7, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback import formencode @@ -63,7 +64,7 @@ super(PullrequestsController, self).__before__() repo_model = RepoModel() c.users_array = repo_model.get_users_js() - c.users_groups_array = repo_model.get_users_groups_js() + c.user_groups_array = repo_model.get_user_groups_js() def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None): """return a structure with repo's interesting changesets, suitable for @@ -106,7 +107,8 @@ if branch == abranch: selected = n branch = None - if branch: # branch not in list - it is probably closed + + if branch: # branch not in list - it is probably closed revs = repo._repo.revs('max(branch(%s))', branch) if revs: cs = repo.get_changeset(revs[0]) @@ -124,7 +126,7 @@ for tag, tagrev in repo.tags.iteritems(): n = 'tag:%s:%s' % (tag, tagrev) tags.append((n, tag)) - if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better + if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better selected = n # prio 1: rev was selected as existing entry above @@ -141,7 +143,14 @@ # prio 4: tip revision if not selected: - selected = 'tag:tip:%s' % repo.tags['tip'] + if h.is_hg(repo): + selected = 'tag:tip:%s' % repo.tags['tip'] + else: + if 'master' in repo.branches: + selected = 'branch:master:%s' % repo.branches['master'] + else: + k, v = repo.branches.items()[0] + selected = 'branch:%s:%s' % (k, v) groups = [(specials, _("Special")), (peers, _("Peer branches")), @@ -155,7 +164,7 @@ owner = self.rhodecode_user.user_id == pull_request.user_id reviewer = self.rhodecode_user.user_id in [x.user_id for x in pull_request.reviewers] - return (self.rhodecode_user.admin or owner or reviewer) + return self.rhodecode_user.admin or owner or reviewer def _load_compare_data(self, pull_request, enable_comments=True): """ @@ -248,10 +257,6 @@ def index(self): org_repo = c.rhodecode_db_repo - if org_repo.scm_instance.alias != 'hg': - log.error('Review not available for GIT REPOS') - raise HTTPNotFound - try: org_repo.scm_instance.get_changeset() except EmptyRepositoryError, e: @@ -366,9 +371,10 @@ raise HTTPForbidden() #only owner or admin can update it owner = pull_request.author.user_id == c.rhodecode_user.user_id - if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: + repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) + if h.HasPermissionAny('hg.admin') or repo_admin or owner: reviewers_ids = map(int, filter(lambda v: v not in [None, ''], - request.POST.get('reviewers_ids', '').split(','))) + request.POST.get('reviewers_ids', '').split(','))) PullRequestModel().update_reviewers(pull_request_id, reviewers_ids) Session().commit() @@ -388,7 +394,7 @@ Session().commit() h.flash(_('Successfully deleted pull request'), category='success') - return redirect(url('admin_settings_my_account', anchor='pullrequests')) + return redirect(url('my_account_pullrequests')) raise HTTPForbidden() @LoginRequired() @@ -397,7 +403,7 @@ def show(self, repo_name, pull_request_id): repo_model = RepoModel() c.users_array = repo_model.get_users_js() - c.users_groups_array = repo_model.get_users_groups_js() + c.user_groups_array = repo_model.get_user_groups_js() c.pull_request = PullRequest.get_or_404(pull_request_id) c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request) cc_model = ChangesetCommentsModel() @@ -544,7 +550,8 @@ raise HTTPForbidden() owner = co.author.user_id == c.rhodecode_user.user_id - if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: + repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) + if h.HasPermissionAny('hg.admin') or repo_admin or owner: ChangesetCommentsModel().delete(comment=co) Session().commit() return True diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/search.py --- a/rhodecode/controllers/search.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/search.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.search - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Search controller for RhodeCode - - :created_on: Aug 7, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.search +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Search controller for RhodeCode + +:created_on: Aug 7, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback import urllib diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/summary.py --- a/rhodecode/controllers/summary.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/summary.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.summary - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Summary controller for Rhodecode - - :created_on: Apr 18, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.summary +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Summary controller for Rhodecode + +:created_on: Apr 18, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import traceback import calendar @@ -37,12 +37,11 @@ from beaker.cache import cache_region, region_invalidate -from rhodecode.lib import helpers as h from rhodecode.lib.compat import product from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \ NodeDoesNotExistError from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP -from rhodecode.model.db import Statistics, CacheInvalidation +from rhodecode.model.db import Statistics, CacheInvalidation, User from rhodecode.lib.utils import jsonify from rhodecode.lib.utils2 import safe_unicode, safe_str from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\ @@ -52,8 +51,7 @@ from rhodecode.lib.markup_renderer import MarkupRenderer from rhodecode.lib.celerylib import run_task from rhodecode.lib.celerylib.tasks import get_commits_stats -from rhodecode.lib.helpers import RepoPage -from rhodecode.lib.compat import json, OrderedDict +from rhodecode.lib.compat import json from rhodecode.lib.vcs.nodes import FileNode from rhodecode.controllers.changelog import _load_changelog_summary @@ -90,12 +88,12 @@ def __get_readme_data(self, db_repo): repo_name = db_repo.repo_name + log.debug('Looking for README file') @cache_region('long_term') def _get_readme_from_cache(key, kind): readme_data = None readme_file = None - log.debug('Looking for README file') try: # gets the landing revision! or tip if fails cs = db_repo.get_landing_changeset() @@ -110,7 +108,8 @@ readme_file = f log.debug('Found README file `%s` rendering...' % readme_file) - readme_data = renderer.render(readme.content, f) + readme_data = renderer.render(readme.content, + filename=f) break except NodeDoesNotExistError: continue @@ -134,40 +133,75 @@ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') def index(self, repo_name): - c.dbrepo = dbrepo = c.rhodecode_db_repo _load_changelog_summary() - if self.rhodecode_user.username == 'default': - # for default(anonymous) user we don't need to pass credentials - username = '' - password = '' + + username = '' + if self.rhodecode_user.username != User.DEFAULT_USER: + username = safe_str(self.rhodecode_user.username) + + _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl + if '{repo}' in _def_clone_uri: + _def_clone_uri_by_id = _def_clone_uri.replace('{repo}', '_{repoid}') + elif '{repoid}' in _def_clone_uri: + _def_clone_uri_by_id = _def_clone_uri.replace('_{repoid}', '{repo}') + + c.clone_repo_url = c.rhodecode_db_repo.clone_url(user=username, + uri_tmpl=_def_clone_uri) + c.clone_repo_url_id = c.rhodecode_db_repo.clone_url(user=username, + uri_tmpl=_def_clone_uri_by_id) + + if c.rhodecode_db_repo.enable_statistics: + c.show_stats = True else: - username = str(self.rhodecode_user.username) - password = '@' + c.show_stats = False + + stats = self.sa.query(Statistics)\ + .filter(Statistics.repository == c.rhodecode_db_repo)\ + .scalar() - parsed_url = urlparse(url.current(qualified=True)) + c.stats_percentage = 0 - default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}' + if stats and stats.languages: + c.no_data = False is c.rhodecode_db_repo.enable_statistics + lang_stats_d = json.loads(stats.languages) - uri_tmpl = config.get('clone_uri', default_clone_uri) - uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s') - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - uri_dict = { - 'user': urllib.quote(username), - 'pass': password, - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'path': urllib.quote(safe_str(decoded_path)) - } + lang_stats = ((x, {"count": y, + "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) + for x, y in lang_stats_d.items()) + + c.trending_languages = json.dumps( + sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10] + ) + else: + c.no_data = True + c.trending_languages = json.dumps({}) + + c.enable_downloads = c.rhodecode_db_repo.enable_downloads + c.readme_data, c.readme_file = \ + self.__get_readme_data(c.rhodecode_db_repo) + return render('summary/summary.html') - uri = (uri_tmpl % uri_dict) - # generate another clone url by id - uri_dict.update( - {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)} - ) - uri_id = uri_tmpl % uri_dict + @LoginRequired() + @NotAnonymous() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + @jsonify + def repo_size(self, repo_name): + if request.is_xhr: + return c.rhodecode_db_repo._repo_size() + else: + raise HTTPBadRequest() - c.clone_repo_url = uri - c.clone_repo_url_id = uri_id + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + def statistics(self, repo_name): + if c.rhodecode_db_repo.enable_statistics: + c.show_stats = True + c.no_data_msg = _('No data loaded yet') + else: + c.show_stats = False + c.no_data_msg = _('Statistics are disabled for this repository') td = date.today() + timedelta(days=1) td_1m = td - timedelta(days=calendar.mdays[td.month]) @@ -176,27 +210,14 @@ ts_min_m = mktime(td_1m.timetuple()) ts_min_y = mktime(td_1y.timetuple()) ts_max_y = mktime(td.timetuple()) - - if dbrepo.enable_statistics: - c.show_stats = True - c.no_data_msg = _('No data loaded yet') - recurse_limit = 500 # don't recurse more than 500 times when parsing - run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, - ts_max_y, recurse_limit) - else: - c.show_stats = False - c.no_data_msg = _('Statistics are disabled for this repository') c.ts_min = ts_min_m c.ts_max = ts_max_y stats = self.sa.query(Statistics)\ - .filter(Statistics.repository == dbrepo)\ + .filter(Statistics.repository == c.rhodecode_db_repo)\ .scalar() - - c.stats_percentage = 0 - if stats and stats.languages: - c.no_data = False is dbrepo.enable_statistics + c.no_data = False is c.rhodecode_db_repo.enable_statistics lang_stats_d = json.loads(stats.languages) c.commit_data = stats.commit_activity c.overview_data = stats.commit_activity_combined @@ -222,21 +243,7 @@ c.trending_languages = json.dumps({}) c.no_data = True - c.enable_downloads = dbrepo.enable_downloads - if c.enable_downloads: - c.download_options = self._get_download_links(c.rhodecode_repo) - - c.readme_data, c.readme_file = \ - self.__get_readme_data(c.rhodecode_db_repo) - return render('summary/summary.html') - - @LoginRequired() - @NotAnonymous() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - @jsonify - def repo_size(self, repo_name): - if request.is_xhr: - return c.rhodecode_db_repo._repo_size() - else: - raise HTTPBadRequest() + recurse_limit = 500 # don't recurse more than 500 times when parsing + run_task(get_commits_stats, c.rhodecode_db_repo.repo_name, ts_min_y, + ts_max_y, recurse_limit) + return render('summary/statistics.html') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/controllers/tags.py --- a/rhodecode/controllers/tags.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/controllers/tags.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.tags - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Tags controller for rhodecode - - :created_on: Apr 21, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.controllers.tags +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tags controller for rhodecode + +:created_on: Apr 21, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + import logging from pylons import tmpl_context as c diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/__init__.py --- a/rhodecode/lib/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,6 +1,30 @@ +# -*- 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 . +""" +rhodecode.lib +~~~~~~~~~~~~~ + +RhodeCode libs + +:created_on: Oct 06, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os - def get_current_revision(quiet=False): """ Returns tuple of (number, id) from repository containing this package diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/annotate.py --- a/rhodecode/lib/annotate.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/annotate.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,28 @@ # -*- 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 . """ - rhodecode.lib.annotate - ~~~~~~~~~~~~~~~~~~~~~~ +rhodecode.lib.annotate +~~~~~~~~~~~~~~~~~~~~~~ - Anontation library for usage in rhodecode, previously part of vcs +Anontation library for usage in rhodecode, previously part of vcs - :created_on: Dec 4, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. +:created_on: Dec 4, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. """ + import StringIO from rhodecode.lib.vcs.exceptions import VCSError diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/app_globals.py --- a/rhodecode/lib/app_globals.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/app_globals.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,13 +1,37 @@ -"""The application's Globals object""" +# -*- 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 . + +""" +rhodecode.lib.app_globals +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The application's Globals object + +:created_on: Oct 06, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" from beaker.cache import CacheManager from beaker.util import parse_cache_config_options class Globals(object): - """Globals acts as a container for objects available throughout the + """ + Globals acts as a container for objects available throughout the life of the application - """ def __init__(self, config): diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/auth.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.auth - ~~~~~~~~~~~~~~~~~~ - - authentication and permission libraries - - :created_on: Apr 4, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,34 +11,52 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.auth +~~~~~~~~~~~~~~~~~~ +authentication and permission libraries + +:created_on: Apr 4, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" +from __future__ import with_statement +import time import random import logging import traceback import hashlib +import itertools +import collections from tempfile import _RandomNameSequence from decorator import decorator -from pylons import config, url, request +from pylons import url, request from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ +from sqlalchemy import or_ from sqlalchemy.orm.exc import ObjectDeletedError +from sqlalchemy.orm import joinedload from rhodecode import __platform__, is_windows, is_unix +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.model import meta from rhodecode.model.meta import Session +from rhodecode.model.user import UserModel +from rhodecode.model.db import User, Repository, Permission, \ + UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \ + RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \ + UserGroup, UserApiKeys -from rhodecode.lib.utils2 import str2bool, safe_unicode, aslist -from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError,\ - LdapImportError -from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug,\ - get_user_group_slug -from rhodecode.lib.auth_ldap import AuthLdap +from rhodecode.lib.utils2 import safe_unicode, aslist +from rhodecode.lib.utils import get_repo_slug, get_repo_group_slug, \ + get_user_group_slug, conditional_cache +from rhodecode.lib.caching_query import FromCache -from rhodecode.model import meta -from rhodecode.model.user import UserModel -from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap -from rhodecode.lib.caching_query import FromCache +from beaker.cache import cache_region log = logging.getLogger(__name__) @@ -149,166 +156,6 @@ return hashlib.sha1(str_ + salt).hexdigest() -def authfunc(environ, username, password): - """ - Dummy authentication wrapper function used in Mercurial and Git for - access control. - - :param environ: needed only for using in Basic auth - """ - return authenticate(username, password) - - -def authenticate(username, password): - """ - Authentication function used for access control, - firstly checks for db authentication then if ldap is enabled for ldap - authentication, also creates ldap user if not in database - - :param username: username - :param password: password - """ - - user_model = UserModel() - user = User.get_by_username(username) - - log.debug('Authenticating user using RhodeCode account') - if user is not None and not user.ldap_dn: - if user.active: - if user.username == 'default' and user.active: - log.info('user %s authenticated correctly as anonymous user' % - username) - return True - - elif user.username == username and check_password(password, - user.password): - log.info('user %s authenticated correctly' % username) - return True - else: - log.warning('user %s tried auth but is disabled' % username) - - else: - log.debug('Regular authentication failed') - user_obj = User.get_by_username(username, case_insensitive=True) - - if user_obj is not None and not user_obj.ldap_dn: - log.debug('this user already exists as non ldap') - return False - - ldap_settings = RhodeCodeSetting.get_ldap_settings() - #====================================================================== - # FALLBACK TO LDAP AUTH IF ENABLE - #====================================================================== - if str2bool(ldap_settings.get('ldap_active')): - log.debug("Authenticating user using ldap") - kwargs = { - 'server': ldap_settings.get('ldap_host', ''), - 'base_dn': ldap_settings.get('ldap_base_dn', ''), - 'port': ldap_settings.get('ldap_port'), - 'bind_dn': ldap_settings.get('ldap_dn_user'), - 'bind_pass': ldap_settings.get('ldap_dn_pass'), - 'tls_kind': ldap_settings.get('ldap_tls_kind'), - 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'), - 'ldap_filter': ldap_settings.get('ldap_filter'), - 'search_scope': ldap_settings.get('ldap_search_scope'), - 'attr_login': ldap_settings.get('ldap_attr_login'), - 'ldap_version': 3, - } - log.debug('Checking for ldap authentication') - try: - aldap = AuthLdap(**kwargs) - (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, - password) - log.debug('Got ldap DN response %s' % user_dn) - - get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\ - .get(k), [''])[0] - - user_attrs = { - 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')), - 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')), - 'email': get_ldap_attr('ldap_attr_email'), - 'active': 'hg.extern_activate.auto' in User.get_default_user()\ - .AuthUser.permissions['global'] - } - - # don't store LDAP password since we don't need it. Override - # with some random generated password - _password = PasswordGenerator().gen_password(length=8) - # create this user on the fly if it doesn't exist in rhodecode - # database - if user_model.create_ldap(username, _password, user_dn, - user_attrs): - log.info('created new ldap user %s' % username) - - Session().commit() - return True - except (LdapUsernameError, LdapPasswordError, LdapImportError): - pass - except (Exception,): - log.error(traceback.format_exc()) - pass - return False - - -def login_container_auth(username): - user = User.get_by_username(username) - if user is None: - user_attrs = { - 'name': username, - 'lastname': None, - 'email': None, - 'active': 'hg.extern_activate.auto' in User.get_default_user()\ - .AuthUser.permissions['global'] - } - user = UserModel().create_for_container_auth(username, user_attrs) - if not user: - return None - log.info('User %s was created by container authentication' % username) - - if not user.active: - return None - - user.update_lastlogin() - Session().commit() - - log.debug('User %s is now logged in by container authentication', - user.username) - return user - - -def get_container_username(environ, config, clean_username=False): - """ - Gets the container_auth username (or email). It tries to get username - from REMOTE_USER if container_auth_enabled is enabled, if that fails - it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled - is enabled. clean_username extracts the username from this data if it's - having @ in it. - - :param environ: - :param config: - :param clean_username: - """ - username = None - - if str2bool(config.get('container_auth_enabled', False)): - from paste.httpheaders import REMOTE_USER - username = REMOTE_USER(environ) - log.debug('extracted REMOTE_USER:%s' % (username)) - - if not username and str2bool(config.get('proxypass_auth_enabled', False)): - username = environ.get('HTTP_X_FORWARDED_USER') - log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username)) - - if username and clean_username: - # Removing realm and domain from username - username = username.partition('@')[0] - username = username.rpartition('\\')[2] - log.debug('Received username %s from container' % username) - - return username - - class CookieStoreWrapper(object): def __init__(self, cookie_store): @@ -324,60 +171,361 @@ return self.cookie_store.__dict__.get(key, other) -class AuthUser(object): + +def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions, + explicit, algo): + RK = 'repositories' + GK = 'repositories_groups' + UK = 'user_groups' + GLOBAL = 'global' + PERM_WEIGHTS = Permission.PERM_WEIGHTS + permissions = {RK: {}, GK: {}, UK: {}, GLOBAL: set()} + + def _choose_perm(new_perm, cur_perm): + new_perm_val = PERM_WEIGHTS[new_perm] + cur_perm_val = PERM_WEIGHTS[cur_perm] + if algo == 'higherwin': + if new_perm_val > cur_perm_val: + return new_perm + return cur_perm + elif algo == 'lowerwin': + if new_perm_val < cur_perm_val: + return new_perm + return cur_perm + + #====================================================================== + # fetch default permissions + #====================================================================== + default_user = User.get_by_username('default', cache=True) + default_user_id = default_user.user_id + + default_repo_perms = Permission.get_default_perms(default_user_id) + default_repo_groups_perms = Permission.get_default_group_perms(default_user_id) + default_user_group_perms = Permission.get_default_user_group_perms(default_user_id) + + if user_is_admin: + #================================================================== + # admin user have all default rights for repositories + # and groups set to admin + #================================================================== + permissions[GLOBAL].add('hg.admin') + permissions[GLOBAL].add('hg.create.write_on_repogroup.true') + + # repositories + for perm in default_repo_perms: + r_k = perm.UserRepoToPerm.repository.repo_name + p = 'repository.admin' + permissions[RK][r_k] = p + + # repository groups + for perm in default_repo_groups_perms: + rg_k = perm.UserRepoGroupToPerm.group.group_name + p = 'group.admin' + permissions[GK][rg_k] = p + + # user groups + for perm in default_user_group_perms: + u_k = perm.UserUserGroupToPerm.user_group.users_group_name + p = 'usergroup.admin' + permissions[UK][u_k] = p + return permissions + + #================================================================== + # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS + #================================================================== + uid = user_id + + # default global permissions taken fron the default user + default_global_perms = UserToPerm.query()\ + .filter(UserToPerm.user_id == default_user_id)\ + .options(joinedload(UserToPerm.permission)) + + for perm in default_global_perms: + permissions[GLOBAL].add(perm.permission.permission_name) + + # defaults for repositories, taken from default user + for perm in default_repo_perms: + r_k = perm.UserRepoToPerm.repository.repo_name + if perm.Repository.private and not (perm.Repository.user_id == uid): + # disable defaults for private repos, + p = 'repository.none' + elif perm.Repository.user_id == uid: + # set admin if owner + p = 'repository.admin' + else: + p = perm.Permission.permission_name + + permissions[RK][r_k] = p + + # defaults for repository groups taken from default user permission + # on given group + for perm in default_repo_groups_perms: + rg_k = perm.UserRepoGroupToPerm.group.group_name + p = perm.Permission.permission_name + permissions[GK][rg_k] = p + + # defaults for user groups taken from default user permission + # on given user group + for perm in default_user_group_perms: + u_k = perm.UserUserGroupToPerm.user_group.users_group_name + p = perm.Permission.permission_name + permissions[UK][u_k] = p + + #====================================================================== + # !! OVERRIDE GLOBALS !! with user permissions if any found + #====================================================================== + # those can be configured from groups or users explicitly + _configurable = set([ + 'hg.fork.none', 'hg.fork.repository', + 'hg.create.none', 'hg.create.repository', + 'hg.usergroup.create.false', 'hg.usergroup.create.true' + ]) + + # USER GROUPS comes first + # user group global permissions + user_perms_from_users_groups = Session().query(UserGroupToPerm)\ + .options(joinedload(UserGroupToPerm.permission))\ + .join((UserGroupMember, UserGroupToPerm.users_group_id == + UserGroupMember.users_group_id))\ + .filter(UserGroupMember.user_id == uid)\ + .order_by(UserGroupToPerm.users_group_id)\ + .all() + # need to group here by groups since user can be in more than + # one group + _grouped = [[x, list(y)] for x, y in + itertools.groupby(user_perms_from_users_groups, + lambda x:x.users_group)] + for gr, perms in _grouped: + # since user can be in multiple groups iterate over them and + # select the lowest permissions first (more explicit) + ##TODO: do this^^ + if not gr.inherit_default_permissions: + # NEED TO IGNORE all configurable permissions and + # replace them with explicitly set + permissions[GLOBAL] = permissions[GLOBAL]\ + .difference(_configurable) + for perm in perms: + permissions[GLOBAL].add(perm.permission.permission_name) + + # user specific global permissions + user_perms = Session().query(UserToPerm)\ + .options(joinedload(UserToPerm.permission))\ + .filter(UserToPerm.user_id == uid).all() + + if not user_inherit_default_permissions: + # NEED TO IGNORE all configurable permissions and + # replace them with explicitly set + permissions[GLOBAL] = permissions[GLOBAL]\ + .difference(_configurable) + + for perm in user_perms: + permissions[GLOBAL].add(perm.permission.permission_name) + ## END GLOBAL PERMISSIONS + + #====================================================================== + # !! PERMISSIONS FOR REPOSITORIES !! + #====================================================================== + #====================================================================== + # check if user is part of user groups for this repository and + # fill in his permission from it. _choose_perm decides of which + # permission should be selected based on selected method + #====================================================================== + + # user group for repositories permissions + user_repo_perms_from_users_groups = \ + Session().query(UserGroupRepoToPerm, Permission, Repository,)\ + .join((Repository, UserGroupRepoToPerm.repository_id == + Repository.repo_id))\ + .join((Permission, UserGroupRepoToPerm.permission_id == + Permission.permission_id))\ + .join((UserGroupMember, UserGroupRepoToPerm.users_group_id == + UserGroupMember.users_group_id))\ + .filter(UserGroupMember.user_id == uid)\ + .all() + + multiple_counter = collections.defaultdict(int) + for perm in user_repo_perms_from_users_groups: + r_k = perm.UserGroupRepoToPerm.repository.repo_name + multiple_counter[r_k] += 1 + p = perm.Permission.permission_name + cur_perm = permissions[RK][r_k] + + if perm.Repository.user_id == uid: + # set admin if owner + p = 'repository.admin' + else: + if multiple_counter[r_k] > 1: + p = _choose_perm(p, cur_perm) + permissions[RK][r_k] = p + + # user explicit permissions for repositories, overrides any specified + # by the group permission + user_repo_perms = Permission.get_default_perms(uid) + for perm in user_repo_perms: + r_k = perm.UserRepoToPerm.repository.repo_name + cur_perm = permissions[RK][r_k] + # set admin if owner + if perm.Repository.user_id == uid: + p = 'repository.admin' + else: + p = perm.Permission.permission_name + if not explicit: + p = _choose_perm(p, cur_perm) + permissions[RK][r_k] = p + + #====================================================================== + # !! PERMISSIONS FOR REPOSITORY GROUPS !! + #====================================================================== + #====================================================================== + # check if user is part of user groups for this repository groups and + # fill in his permission from it. _choose_perm decides of which + # permission should be selected based on selected method + #====================================================================== + # user group for repo groups permissions + user_repo_group_perms_from_users_groups = \ + Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\ + .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((Permission, UserGroupRepoGroupToPerm.permission_id + == Permission.permission_id))\ + .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id + == UserGroupMember.users_group_id))\ + .filter(UserGroupMember.user_id == uid)\ + .all() + + multiple_counter = collections.defaultdict(int) + for perm in user_repo_group_perms_from_users_groups: + g_k = perm.UserGroupRepoGroupToPerm.group.group_name + multiple_counter[g_k] += 1 + p = perm.Permission.permission_name + cur_perm = permissions[GK][g_k] + if multiple_counter[g_k] > 1: + p = _choose_perm(p, cur_perm) + permissions[GK][g_k] = p + + # user explicit permissions for repository groups + user_repo_groups_perms = Permission.get_default_group_perms(uid) + for perm in user_repo_groups_perms: + rg_k = perm.UserRepoGroupToPerm.group.group_name + p = perm.Permission.permission_name + cur_perm = permissions[GK][rg_k] + if not explicit: + p = _choose_perm(p, cur_perm) + permissions[GK][rg_k] = p + + #====================================================================== + # !! PERMISSIONS FOR USER GROUPS !! + #====================================================================== + # user group for user group permissions + user_group_user_groups_perms = \ + Session().query(UserGroupUserGroupToPerm, Permission, UserGroup)\ + .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id + == UserGroup.users_group_id))\ + .join((Permission, UserGroupUserGroupToPerm.permission_id + == Permission.permission_id))\ + .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id + == UserGroupMember.users_group_id))\ + .filter(UserGroupMember.user_id == uid)\ + .all() + + multiple_counter = collections.defaultdict(int) + for perm in user_group_user_groups_perms: + g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name + multiple_counter[g_k] += 1 + p = perm.Permission.permission_name + cur_perm = permissions[UK][g_k] + if multiple_counter[g_k] > 1: + p = _choose_perm(p, cur_perm) + permissions[UK][g_k] = p + + #user explicit permission for user groups + user_user_groups_perms = Permission.get_default_user_group_perms(uid) + for perm in user_user_groups_perms: + u_k = perm.UserUserGroupToPerm.user_group.users_group_name + p = perm.Permission.permission_name + cur_perm = permissions[UK][u_k] + if not explicit: + p = _choose_perm(p, cur_perm) + permissions[UK][u_k] = p + + return permissions + + +def allowed_api_access(controller_name, whitelist=None, api_key=None): + """ + Check if given controller_name is in whitelist API access + """ + if not whitelist: + from rhodecode import CONFIG + whitelist = aslist(CONFIG.get('api_access_controllers_whitelist'), + sep=',') + log.debug('whitelist of API access is: %s' % (whitelist)) + api_access_valid = controller_name in whitelist + if api_access_valid: + log.debug('controller:%s is in API whitelist' % (controller_name)) + else: + msg = 'controller: %s is *NOT* in API whitelist' % (controller_name) + if api_key: + #if we use API key and don't have access it's a warning + log.warning(msg) + else: + log.debug(msg) + return api_access_valid + + +class AuthUser(object): """ A simple object that handles all attributes of user in RhodeCode It does lookup based on API key,given user, or user present in session Then it fills all required information for such user. It also checks if - anonymous access is enabled and if so, it returns default user as logged - in + anonymous access is enabled and if so, it returns default user as logged in """ def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None): self.user_id = user_id + self._api_key = api_key + self.api_key = None self.username = username self.ip_addr = ip_addr - self.name = '' self.lastname = '' self.email = '' self.is_authenticated = False self.admin = False self.inherit_default_permissions = False - self.permissions = {} - self._api_key = api_key + self.propagate_data() self._instance = None + @LazyProperty + def permissions(self): + return self.get_perms(user=self, cache=False) + + @property + def api_keys(self): + return self.get_api_keys() + def propagate_data(self): user_model = UserModel() - self.anonymous_user = User.get_by_username('default', cache=True) + self.anonymous_user = User.get_default_user(cache=True) is_user_loaded = False - # try go get user by api key - if self._api_key and self._api_key != self.anonymous_user.api_key: - log.debug('Auth User lookup by API KEY %s' % self._api_key) - is_user_loaded = user_model.fill_data(self, api_key=self._api_key) # lookup by userid - elif (self.user_id is not None and - self.user_id != self.anonymous_user.user_id): + if self.user_id is not None and self.user_id != self.anonymous_user.user_id: log.debug('Auth User lookup by USER ID %s' % self.user_id) is_user_loaded = user_model.fill_data(self, user_id=self.user_id) + + # try go get user by api key + elif self._api_key and self._api_key != self.anonymous_user.api_key: + log.debug('Auth User lookup by API KEY %s' % self._api_key) + is_user_loaded = user_model.fill_data(self, api_key=self._api_key) + # lookup by username - elif self.username and \ - str2bool(config.get('container_auth_enabled', False)): - + elif self.username: log.debug('Auth User lookup by USER NAME %s' % self.username) - dbuser = login_container_auth(self.username) - if dbuser is not None: - log.debug('filling all attributes to object') - for k, v in dbuser.get_dict().items(): - setattr(self, k, v) - self.set_authenticated() - is_user_loaded = True + is_user_loaded = user_model.fill_data(self, username=self.username) else: log.debug('No data in %s that could been used to log in' % self) @@ -396,7 +544,42 @@ self.username = 'None' log.debug('Auth User is now %s' % self) - user_model.fill_perms(self) + + def get_perms(self, user, explicit=True, algo='higherwin', cache=False): + """ + Fills user permission attribute with permissions taken from database + works for permissions given for repositories, and for permissions that + are granted to groups + + :param user: instance of User object from database + :param explicit: In case there are permissions both for user and a group + that user is part of, explicit flag will defiine if user will + explicitly override permissions from group, if it's False it will + make decision based on the algo + :param algo: algorithm to decide what permission should be choose if + it's multiple defined, eg user in two different groups. It also + decides if explicit flag is turned off how to specify the permission + for case when user is in a group + have defined separate permission + """ + user_id = user.user_id + user_is_admin = user.is_admin + user_inherit_default_permissions = user.inherit_default_permissions + + log.debug('Getting PERMISSION tree') + compute = conditional_cache('short_term', 'cache_desc', + condition=cache, func=_cached_perms_data) + return compute(user_id, user_is_admin, + user_inherit_default_permissions, explicit, algo) + + def get_api_keys(self): + api_keys = [self.api_key] + for api_key in UserApiKeys.query()\ + .filter(UserApiKeys.user_id == self.user_id)\ + .filter(or_(UserApiKeys.expires == -1, + UserApiKeys.expires >= time.time())).all(): + api_keys.append(api_key.api_key) + + return api_keys @property def is_admin(self): @@ -434,14 +617,21 @@ :returns: boolean, True if ip is in allowed ip range """ - #check IP - allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True) - if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips): - log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips)) + # check IP + inherit = self.inherit_default_permissions + return AuthUser.check_ip_allowed(self.user_id, self.ip_addr, + inherit_from_default=inherit) + + @classmethod + def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default): + allowed_ips = AuthUser.get_allowed_ips(user_id, cache=True, + inherit_from_default=inherit_from_default) + if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips): + log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips)) return True else: log.info('Access for IP:%s forbidden, ' - 'not in %s' % (self.ip_addr, allowed_ips)) + 'not in %s' % (ip_addr, allowed_ips)) return False def __repr__(self): @@ -471,12 +661,30 @@ return AuthUser(user_id, api_key, username) @classmethod - def get_allowed_ips(cls, user_id, cache=False): + def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False): _set = set() + + if inherit_from_default: + default_ips = UserIpMap.query().filter(UserIpMap.user == + User.get_default_user(cache=True)) + if cache: + default_ips = default_ips.options(FromCache("sql_cache_short", + "get_user_ips_default")) + + # populate from default user + for ip in default_ips: + try: + _set.add(ip.ip_addr) + except ObjectDeletedError: + # since we use heavy caching sometimes it happens that we get + # deleted objects here, we just skip them + pass + user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id) if cache: user_ips = user_ips.options(FromCache("sql_cache_short", "get_user_ips_%s" % user_id)) + for ip in user_ips: try: _set.add(ip.ip_addr) @@ -530,38 +738,45 @@ cls = fargs[0] user = cls.rhodecode_user loc = "%s:%s" % (cls.__class__.__name__, func.__name__) - # defined whitelist of controllers which API access will be enabled - whitelist = aslist(config.get('api_access_controllers_whitelist'), - sep=',') - api_access_whitelist = loc in whitelist - log.debug('loc:%s is in API whitelist:%s:%s' % (loc, whitelist, - api_access_whitelist)) - #check IP - ip_access_ok = True + + # check if our IP is allowed + ip_access_valid = True if not user.ip_allowed: from rhodecode.lib import helpers as h h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))), category='warning') - ip_access_ok = False + ip_access_valid = False - api_access_ok = False - if self.api_access or api_access_whitelist: + # check if we used an APIKEY and it's a valid one + # defined whitelist of controllers which API access will be enabled + _api_key = request.GET.get('api_key', '') + api_access_valid = allowed_api_access(loc, api_key=_api_key) + + # explicit controller is enabled or API is in our whitelist + if self.api_access or api_access_valid: log.debug('Checking API KEY access for %s' % cls) - if user.api_key == request.GET.get('api_key'): - api_access_ok = True + if _api_key and _api_key in user.api_keys: + api_access_valid = True + log.debug('API KEY ****%s is VALID' % _api_key[-4:]) else: - log.debug("API KEY token not valid") + api_access_valid = False + if not _api_key: + log.debug("API KEY *NOT* present in request") + else: + log.warn("API KEY ****%s *NOT* valid" % _api_key[-4:]) log.debug('Checking if %s is authenticated @ %s' % (user.username, loc)) - if (user.is_authenticated or api_access_ok) and ip_access_ok: - reason = 'RegularAuth' if user.is_authenticated else 'APIAuth' - log.info('user %s is authenticated and granted access to %s ' - 'using %s' % (user.username, loc, reason) + reason = 'RegularAuth' if user.is_authenticated else 'APIAuth' + + if ip_access_valid and (user.is_authenticated or api_access_valid): + log.info('user %s authenticating with:%s IS authenticated on func %s ' + % (user, reason, loc) ) return func(*fargs, **fkwargs) else: - log.warn('user %s NOT authenticated on func: %s' % ( - user, loc) + log.warn('user %s authenticating with:%s NOT authenticated on func: %s: ' + 'IP_ACCESS:%s API_ACCESS:%s' + % (user, reason, loc, ip_access_valid, api_access_valid) ) p = url.current() @@ -583,7 +798,7 @@ log.debug('Checking if user is not anonymous @%s' % cls) - anonymous = self.user.username == 'default' + anonymous = self.user.username == User.DEFAULT_USER if anonymous: p = url.current() @@ -601,10 +816,6 @@ """Base class for controller decorators""" def __init__(self, *required_perms): - available_perms = config['available_permissions'] - for perm in required_perms: - if perm not in available_perms: - raise Exception("'%s' permission is not defined" % perm) self.required_perms = set(required_perms) self.user_perms = None @@ -624,7 +835,7 @@ else: log.debug('Permission denied for %s %s' % (cls, self.user)) - anonymous = self.user.username == 'default' + anonymous = self.user.username == User.DEFAULT_USER if anonymous: p = url.current() @@ -703,14 +914,14 @@ return False -class HasReposGroupPermissionAllDecorator(PermsDecorator): +class HasRepoGroupPermissionAllDecorator(PermsDecorator): """ Checks for access permission for all given predicates for specific repository group. All of them have to be meet in order to fulfill the request """ def check_permissions(self): - group_name = get_repos_group_slug(request) + group_name = get_repo_group_slug(request) try: user_perms = set([self.user_perms['repositories_groups'][group_name]]) except KeyError: @@ -721,14 +932,14 @@ return False -class HasReposGroupPermissionAnyDecorator(PermsDecorator): +class HasRepoGroupPermissionAnyDecorator(PermsDecorator): """ Checks for access permission for any of given predicates for specific repository group. In order to fulfill the request any of predicates must be meet """ def check_permissions(self): - group_name = get_repos_group_slug(request) + group_name = get_repo_group_slug(request) try: user_perms = set([self.user_perms['repositories_groups'][group_name]]) except KeyError: @@ -782,27 +993,28 @@ """Base function for other check functions""" def __init__(self, *perms): - available_perms = config['available_permissions'] - - for perm in perms: - if perm not in available_perms: - raise Exception("'%s' permission is not defined" % perm) self.required_perms = set(perms) self.user_perms = None self.repo_name = None self.group_name = None - def __call__(self, check_location=''): - #TODO: put user as attribute here - user = request.user + def __call__(self, check_location='', user=None): + if not user: + #TODO: remove this someday,put as user as attribute here + user = request.user + + # init auth user if not already given + if not isinstance(user, AuthUser): + user = AuthUser(user.user_id) + cls_name = self.__class__.__name__ check_scope = { 'HasPermissionAll': '', 'HasPermissionAny': '', 'HasRepoPermissionAll': 'repo:%s' % self.repo_name, 'HasRepoPermissionAny': 'repo:%s' % self.repo_name, - 'HasReposGroupPermissionAll': 'group:%s' % self.group_name, - 'HasReposGroupPermissionAny': 'group:%s' % self.group_name, + 'HasRepoGroupPermissionAll': 'group:%s' % self.group_name, + 'HasRepoGroupPermissionAny': 'group:%s' % self.group_name, }.get(cls_name, '?') log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name, self.required_perms, user, check_scope, @@ -812,13 +1024,15 @@ return False self.user_perms = user.permissions if self.check_permissions(): - log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user, - check_location or 'unspecified location') + log.debug('Permission to %s granted for user: %s @ %s' + % (check_scope, user, + check_location or 'unspecified location')) return True else: - log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user, - check_location or 'unspecified location') + log.debug('Permission to %s denied for user: %s @ %s' + % (check_scope, user, + check_location or 'unspecified location')) return False def check_permissions(self): @@ -841,9 +1055,9 @@ class HasRepoPermissionAll(PermsFunction): - def __call__(self, repo_name=None, check_location=''): + def __call__(self, repo_name=None, check_location='', user=None): self.repo_name = repo_name - return super(HasRepoPermissionAll, self).__call__(check_location) + return super(HasRepoPermissionAll, self).__call__(check_location, user) def check_permissions(self): if not self.repo_name: @@ -861,9 +1075,9 @@ class HasRepoPermissionAny(PermsFunction): - def __call__(self, repo_name=None, check_location=''): + def __call__(self, repo_name=None, check_location='', user=None): self.repo_name = repo_name - return super(HasRepoPermissionAny, self).__call__(check_location) + return super(HasRepoPermissionAny, self).__call__(check_location, user) def check_permissions(self): if not self.repo_name: @@ -880,10 +1094,10 @@ return False -class HasReposGroupPermissionAny(PermsFunction): - def __call__(self, group_name=None, check_location=''): +class HasRepoGroupPermissionAny(PermsFunction): + def __call__(self, group_name=None, check_location='', user=None): self.group_name = group_name - return super(HasReposGroupPermissionAny, self).__call__(check_location) + return super(HasRepoGroupPermissionAny, self).__call__(check_location, user) def check_permissions(self): try: @@ -897,10 +1111,10 @@ return False -class HasReposGroupPermissionAll(PermsFunction): - def __call__(self, group_name=None, check_location=''): +class HasRepoGroupPermissionAll(PermsFunction): + def __call__(self, group_name=None, check_location='', user=None): self.group_name = group_name - return super(HasReposGroupPermissionAll, self).__call__(check_location) + return super(HasRepoGroupPermissionAll, self).__call__(check_location, user) def check_permissions(self): try: @@ -915,9 +1129,9 @@ class HasUserGroupPermissionAny(PermsFunction): - def __call__(self, user_group_name=None, check_location=''): + def __call__(self, user_group_name=None, check_location='', user=None): self.user_group_name = user_group_name - return super(HasUserGroupPermissionAny, self).__call__(check_location) + return super(HasUserGroupPermissionAny, self).__call__(check_location, user) def check_permissions(self): try: @@ -932,9 +1146,9 @@ class HasUserGroupPermissionAll(PermsFunction): - def __call__(self, user_group_name=None, check_location=''): + def __call__(self, user_group_name=None, check_location='', user=None): self.user_group_name = user_group_name - return super(HasUserGroupPermissionAll, self).__call__(check_location) + return super(HasUserGroupPermissionAll, self).__call__(check_location, user) def check_permissions(self): try: @@ -947,6 +1161,7 @@ return True return False + #============================================================================== # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH #============================================================================== @@ -974,15 +1189,11 @@ 'permissions %s for user:%s repository:%s', self.user_perms, self.username, self.repo_name) if self.required_perms.intersection(self.user_perms): - log.debug('permission granted for user:%s on repo:%s' % ( - self.username, self.repo_name - ) - ) + log.debug('Permission to repo: %s granted for user: %s @ %s' + % (self.repo_name, self.username, 'PermissionMiddleware')) return True - log.debug('permission denied for user:%s on repo:%s' % ( - self.username, self.repo_name - ) - ) + log.debug('Permission to repo: %s denied for user: %s @ %s' + % (self.repo_name, self.username, 'PermissionMiddleware')) return False @@ -993,11 +1204,18 @@ def __init__(self, *perms): self.required_perms = set(perms) - def __call__(self, check_location='unspecified', user=None, repo_name=None): + def __call__(self, check_location=None, user=None, repo_name=None, + group_name=None): cls_name = self.__class__.__name__ - check_scope = 'user:%s, repo:%s' % (user, repo_name) - log.debug('checking cls:%s %s %s @ %s', cls_name, - self.required_perms, check_scope, check_location) + check_scope = 'user:%s' % (user) + if repo_name: + check_scope += ', repo:%s' % (repo_name) + + if group_name: + check_scope += ', repo group:%s' % (group_name) + + log.debug('checking cls:%s %s %s @ %s' + % (cls_name, self.required_perms, check_scope, check_location)) if not user: log.debug('Empty User passed into arguments') return False @@ -1005,18 +1223,19 @@ ## process user if not isinstance(user, AuthUser): user = AuthUser(user.user_id) - - if self.check_permissions(user.permissions, repo_name): - log.debug('Permission to %s granted for user: %s @ %s', repo_name, - user, check_location) + if not check_location: + check_location = 'unspecified' + if self.check_permissions(user.permissions, repo_name, group_name): + log.debug('Permission to %s granted for user: %s @ %s' + % (check_scope, user, check_location)) return True else: - log.debug('Permission to %s denied for user: %s @ %s', repo_name, - user, check_location) + log.debug('Permission to %s denied for user: %s @ %s' + % (check_scope, user, check_location)) return False - def check_permissions(self, perm_defs, repo_name): + def check_permissions(self, perm_defs, repo_name=None, group_name=None): """ implement in child class should return True if permissions are ok, False otherwise @@ -1028,59 +1247,35 @@ class HasPermissionAllApi(_BaseApiPerm): - def __call__(self, user, check_location=''): - return super(HasPermissionAllApi, self)\ - .__call__(check_location=check_location, user=user) - - def check_permissions(self, perm_defs, repo): + def check_permissions(self, perm_defs, repo_name=None, group_name=None): if self.required_perms.issubset(perm_defs.get('global')): return True return False class HasPermissionAnyApi(_BaseApiPerm): - def __call__(self, user, check_location=''): - return super(HasPermissionAnyApi, self)\ - .__call__(check_location=check_location, user=user) - - def check_permissions(self, perm_defs, repo): + def check_permissions(self, perm_defs, repo_name=None, group_name=None): if self.required_perms.intersection(perm_defs.get('global')): return True return False class HasRepoPermissionAllApi(_BaseApiPerm): - def __call__(self, user, repo_name, check_location=''): - return super(HasRepoPermissionAllApi, self)\ - .__call__(check_location=check_location, user=user, - repo_name=repo_name) - - def check_permissions(self, perm_defs, repo_name): - + def check_permissions(self, perm_defs, repo_name=None, group_name=None): try: - self._user_perms = set( - [perm_defs['repositories'][repo_name]] - ) + _user_perms = set([perm_defs['repositories'][repo_name]]) except KeyError: log.warning(traceback.format_exc()) return False - if self.required_perms.issubset(self._user_perms): + if self.required_perms.issubset(_user_perms): return True return False class HasRepoPermissionAnyApi(_BaseApiPerm): - def __call__(self, user, repo_name, check_location=''): - return super(HasRepoPermissionAnyApi, self)\ - .__call__(check_location=check_location, user=user, - repo_name=repo_name) - - def check_permissions(self, perm_defs, repo_name): - + def check_permissions(self, perm_defs, repo_name=None, group_name=None): try: - _user_perms = set( - [perm_defs['repositories'][repo_name]] - ) + _user_perms = set([perm_defs['repositories'][repo_name]]) except KeyError: log.warning(traceback.format_exc()) return False @@ -1089,6 +1284,28 @@ return False +class HasRepoGroupPermissionAnyApi(_BaseApiPerm): + def check_permissions(self, perm_defs, repo_name=None, group_name=None): + try: + _user_perms = set([perm_defs['repositories_groups'][group_name]]) + except KeyError: + log.warning(traceback.format_exc()) + return False + if self.required_perms.intersection(_user_perms): + return True + return False + +class HasRepoGroupPermissionAllApi(_BaseApiPerm): + def check_permissions(self, perm_defs, repo_name=None, group_name=None): + try: + _user_perms = set([perm_defs['repositories_groups'][group_name]]) + except KeyError: + log.warning(traceback.format_exc()) + return False + if self.required_perms.issubset(_user_perms): + return True + return False + def check_ip_access(source_ip, allowed_ips=None): """ Checks if source_ip is a subnet of any of allowed_ips. @@ -1102,6 +1319,8 @@ for ip in allowed_ips: try: if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip): + log.debug('IP %s is network %s' % + (ipaddr.IPAddress(source_ip), ipaddr.IPNetwork(ip))) return True # for any case we cannot determine the IP, don't crash just # skip it and log as error, we want to say forbidden still when diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth_ldap.py --- a/rhodecode/lib/auth_ldap.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.controllers.changelog - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - RhodeCode authentication library for LDAP - - :created_on: Created on Nov 17, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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 . - -import logging - -from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \ - LdapPasswordError, LdapImportError -from rhodecode.lib.utils2 import safe_str - -log = logging.getLogger(__name__) - - -try: - import ldap -except ImportError: - # means that python-ldap is not installed - ldap = None - - -class AuthLdap(object): - - def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', - tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, - ldap_filter='(&(objectClass=user)(!(objectClass=computer)))', - search_scope='SUBTREE', attr_login='uid'): - if ldap is None: - raise LdapImportError - - self.ldap_version = ldap_version - ldap_server_type = 'ldap' - - self.TLS_KIND = tls_kind - - if self.TLS_KIND == 'LDAPS': - port = port or 689 - ldap_server_type = ldap_server_type + 's' - - OPT_X_TLS_DEMAND = 2 - self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, - OPT_X_TLS_DEMAND) - # split server into list - self.LDAP_SERVER_ADDRESS = server.split(',') - self.LDAP_SERVER_PORT = port - - # USE FOR READ ONLY BIND TO LDAP SERVER - self.LDAP_BIND_DN = safe_str(bind_dn) - self.LDAP_BIND_PASS = safe_str(bind_pass) - _LDAP_SERVERS = [] - for host in self.LDAP_SERVER_ADDRESS: - _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type, - host.replace(' ', ''), - self.LDAP_SERVER_PORT)) - self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS)) - self.BASE_DN = safe_str(base_dn) - self.LDAP_FILTER = safe_str(ldap_filter) - self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) - self.attr_login = attr_login - - def authenticate_ldap(self, username, password): - """ - Authenticate a user via LDAP and return his/her LDAP properties. - - Raises AuthenticationError if the credentials are rejected, or - EnvironmentError if the LDAP server can't be reached. - - :param username: username - :param password: password - """ - - from rhodecode.lib.helpers import chop_at - - uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS) - - if not password: - log.debug("Attempt to authenticate LDAP user " - "with blank password rejected.") - raise LdapPasswordError() - if "," in username: - raise LdapUsernameError("invalid character in username: ,") - try: - if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, - '/etc/openldap/cacerts') - ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) - ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) - ldap.set_option(ldap.OPT_TIMEOUT, 20) - ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10) - ldap.set_option(ldap.OPT_TIMELIMIT, 15) - if self.TLS_KIND != 'PLAIN': - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT) - server = ldap.initialize(self.LDAP_SERVER) - if self.ldap_version == 2: - server.protocol = ldap.VERSION2 - else: - server.protocol = ldap.VERSION3 - - if self.TLS_KIND == 'START_TLS': - server.start_tls_s() - - if self.LDAP_BIND_DN and self.LDAP_BIND_PASS: - log.debug('Trying simple_bind with password and given DN: %s' - % self.LDAP_BIND_DN) - server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) - - filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, - username) - log.debug("Authenticating %r filter %s at %s", self.BASE_DN, - filter_, self.LDAP_SERVER) - lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, - filter_) - - if not lobjects: - raise ldap.NO_SUCH_OBJECT() - - for (dn, _attrs) in lobjects: - if dn is None: - continue - - try: - log.debug('Trying simple bind with %s' % dn) - server.simple_bind_s(dn, password) - attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, - '(objectClass=*)')[0][1] - break - - except ldap.INVALID_CREDENTIALS: - log.debug( - "LDAP rejected password for user '%s' (%s): %s" % ( - uid, username, dn - ) - ) - - else: - log.debug("No matching LDAP objects for authentication " - "of '%s' (%s)", uid, username) - raise LdapPasswordError() - - except ldap.NO_SUCH_OBJECT: - log.debug("LDAP says no such user '%s' (%s)" % (uid, username)) - raise LdapUsernameError() - except ldap.SERVER_DOWN: - raise LdapConnectionError("LDAP can't access " - "authentication server") - - return (dn, attrs) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth_modules/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/auth_modules/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,414 @@ +# -*- 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 . +""" +Authentication modules +""" + +import logging +import traceback + +from rhodecode.lib.compat import importlib +from rhodecode.lib.utils2 import str2bool +from rhodecode.lib.compat import formatted_json, hybrid_property +from rhodecode.lib.auth import PasswordGenerator +from rhodecode.model.user import UserModel +from rhodecode.model.db import RhodeCodeSetting, User, UserGroup +from rhodecode.model.meta import Session +from rhodecode.model.user_group import UserGroupModel + +log = logging.getLogger(__name__) + + +class LazyFormencode(object): + def __init__(self, formencode_obj, *args, **kwargs): + self.formencode_obj = formencode_obj + self.args = args + self.kwargs = kwargs + + def __call__(self, *args, **kwargs): + from inspect import isfunction + formencode_obj = self.formencode_obj + if isfunction(formencode_obj): + #case we wrap validators into functions + formencode_obj = self.formencode_obj(*args, **kwargs) + return formencode_obj(*self.args, **self.kwargs) + + +class RhodeCodeAuthPluginBase(object): + auth_func_attrs = { + "username": "unique username", + "firstname": "first name", + "lastname": "last name", + "email": "email address", + "groups": '["list", "of", "groups"]', + "extern_name": "name in external source of record", + "extern_type": "type of external source of record", + "admin": 'True|False defines if user should be RhodeCode super admin', + "active": 'True|False defines active state of user internally for RhodeCode', + "active_from_extern": "True|False\None, active state from the external auth, " + "None means use definition from RhodeCode extern_type active value" + } + + @property + def validators(self): + """ + Exposes RhodeCode validators modules + """ + # this is a hack to overcome issues with pylons threadlocals and + # translator object _() not beein registered properly. + class LazyCaller(object): + def __init__(self, name): + self.validator_name = name + + def __call__(self, *args, **kwargs): + from rhodecode.model import validators as v + obj = getattr(v, self.validator_name) + #log.debug('Initializing lazy formencode object: %s' % obj) + return LazyFormencode(obj, *args, **kwargs) + + + class ProxyGet(object): + def __getattribute__(self, name): + return LazyCaller(name) + + return ProxyGet() + + @hybrid_property + def name(self): + """ + Returns the name of this authentication plugin. + + :returns: string + """ + raise NotImplementedError("Not implemented in base class") + + @hybrid_property + def is_container_auth(self): + """ + Returns bool if this module uses container auth. + + This property will trigger an automatic call to authenticate on + a visit to the website or during a push/pull. + + :returns: bool + """ + return False + + def accepts(self, user, accepts_empty=True): + """ + Checks if this authentication module should accept a request for + the current user. + + :param user: user object fetched using plugin's get_user() method. + :param accepts_empty: if True accepts don't allow the user to be empty + :returns: boolean + """ + plugin_name = self.name + if not user and not accepts_empty: + log.debug('User is empty not allowed to authenticate') + return False + + if user and user.extern_type and user.extern_type != plugin_name: + log.debug('User %s should authenticate using %s this is %s, skipping' + % (user, user.extern_type, plugin_name)) + + return False + return True + + def get_user(self, username=None, **kwargs): + """ + Helper method for user fetching in plugins, by default it's using + simple fetch by username, but this method can be custimized in plugins + eg. container auth plugin to fetch user by environ params + + :param username: username if given to fetch from database + :param kwargs: extra arguments needed for user fetching. + """ + user = None + log.debug('Trying to fetch user `%s` from RhodeCode database' + % (username)) + if username: + user = User.get_by_username(username) + if not user: + log.debug('Fallback to fetch user in case insensitive mode') + user = User.get_by_username(username, case_insensitive=True) + else: + log.debug('provided username:`%s` is empty skipping...' % username) + return user + + def settings(self): + """ + Return a list of the form: + [ + { + "name": "OPTION_NAME", + "type": "[bool|password|string|int|select]", + ["values": ["opt1", "opt2", ...]] + "validator": "expr" + "description": "A short description of the option" [, + "default": Default Value], + ["formname": "Friendly Name for Forms"] + } [, ...] + ] + + This is used to interrogate the authentication plugin as to what + settings it expects to be present and configured. + + 'type' is a shorthand notation for what kind of value this option is. + This is primarily used by the auth web form to control how the option + is configured. + bool : checkbox + password : password input box + string : input box + select : single select dropdown + + 'validator' is an lazy instantiated form field validator object, ala + formencode. You need to *call* this object to init the validators. + All calls to RhodeCode validators should be used through self.validators + which is a lazy loading proxy of formencode module. + """ + raise NotImplementedError("Not implemented in base class") + + def plugin_settings(self): + """ + This method is called by the authentication framework, not the .settings() + method. This method adds a few default settings (e.g., "active"), so that + plugin authors don't have to maintain a bunch of boilerplate. + + OVERRIDING THIS METHOD WILL CAUSE YOUR PLUGIN TO FAIL. + """ + + rcsettings = self.settings() + rcsettings.insert(0, { + "name": "enabled", + "validator": self.validators.StringBoolean(if_missing=False), + "type": "bool", + "description": "Enable or Disable this Authentication Plugin", + "formname": "Enabled" + } + ) + return rcsettings + + def user_activation_state(self): + """ + Defines user activation state when creating new users + + :returns: boolean + """ + raise NotImplementedError("Not implemented in base class") + + def auth(self, userobj, username, passwd, settings, **kwargs): + """ + Given a user object (which may be null), username, a plaintext password, + and a settings object (containing all the keys needed as listed in settings()), + authenticate this user's login attempt. + + Return None on failure. On success, return a dictionary of the form: + + see: RhodeCodeAuthPluginBase.auth_func_attrs + This is later validated for correctness + """ + raise NotImplementedError("not implemented in base class") + + def _authenticate(self, userobj, username, passwd, settings, **kwargs): + """ + Wrapper to call self.auth() that validates call on it + + :param userobj: userobj + :param username: username + :param passwd: plaintext password + :param settings: plugin settings + """ + auth = self.auth(userobj, username, passwd, settings, **kwargs) + if auth: + return self._validate_auth_return(auth) + return auth + + def _validate_auth_return(self, ret): + if not isinstance(ret, dict): + raise Exception('returned value from auth must be a dict') + for k in self.auth_func_attrs: + if k not in ret: + raise Exception('Missing %s attribute from returned data' % k) + return ret + + +class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase): + def use_fake_password(self): + """ + Return a boolean that indicates whether or not we should set the user's + password to a random value when it is authenticated by this plugin. + If your plugin provides authentication, then you will generally want this. + + :returns: boolean + """ + raise NotImplementedError("Not implemented in base class") + + def _authenticate(self, userobj, username, passwd, settings, **kwargs): + auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate( + userobj, username, passwd, settings, **kwargs) + if auth: + # maybe plugin will clean the username ? + # we should use the return value + username = auth['username'] + # if user is not active from our extern type we should fail to authe + # this can prevent from creating users in RhodeCode when using + # external authentication, but if it's inactive user we shouldn't + # create that user anyway + if auth['active_from_extern'] is False: + log.warning("User %s authenticated against %s, but is inactive" + % (username, self.__module__)) + return None + + if self.use_fake_password(): + # Randomize the PW because we don't need it, but don't want + # them blank either + passwd = PasswordGenerator().gen_password(length=8) + + log.debug('Updating or creating user info from %s plugin' + % self.name) + user = UserModel().create_or_update( + username=username, + password=passwd, + email=auth["email"], + firstname=auth["firstname"], + lastname=auth["lastname"], + active=auth["active"], + admin=auth["admin"], + extern_name=auth["extern_name"], + extern_type=self.name + ) + Session().flush() + # enforce user is just in given groups, all of them has to be ones + # created from plugins. We store this info in _group_data JSON field + try: + groups = auth['groups'] or [] + UserGroupModel().enforce_groups(user, groups, self.name) + except Exception: + # for any reason group syncing fails, we should proceed with login + log.error(traceback.format_exc()) + Session().commit() + return auth + + +def importplugin(plugin): + """ + Imports and returns the authentication plugin in the module named by plugin + (e.g., plugin='rhodecode.lib.auth_modules.auth_rhodecode'). Returns the + RhodeCodeAuthPluginBase subclass on success, raises exceptions on failure. + + raises: + AttributeError -- no RhodeCodeAuthPlugin class in the module + TypeError -- if the RhodeCodeAuthPlugin is not a subclass of ours RhodeCodeAuthPluginBase + ImportError -- if we couldn't import the plugin at all + """ + log.debug("Importing %s" % plugin) + PLUGIN_CLASS_NAME = "RhodeCodeAuthPlugin" + try: + module = importlib.import_module(plugin) + except (ImportError, TypeError): + log.error(traceback.format_exc()) + # TODO: make this more error prone, if by some accident we screw up + # the plugin name, the crash is preatty bad and hard to recover + raise + + log.debug("Loaded auth plugin from %s (module:%s, file:%s)" + % (plugin, module.__name__, module.__file__)) + + pluginclass = getattr(module, PLUGIN_CLASS_NAME) + if not issubclass(pluginclass, RhodeCodeAuthPluginBase): + raise TypeError("Authentication class %s.RhodeCodeAuthPlugin is not " + "a subclass of %s" % (plugin, RhodeCodeAuthPluginBase)) + return pluginclass + + +def loadplugin(plugin): + """ + Loads and returns an instantiated authentication plugin. + + see: importplugin + """ + plugin = importplugin(plugin)() + if plugin.plugin_settings.im_func != RhodeCodeAuthPluginBase.plugin_settings.im_func: + raise TypeError("Authentication class %s.RhodeCodeAuthPluginBase " + "has overriden the plugin_settings method, which is " + "forbidden." % plugin) + return plugin + + +def authenticate(username, password, environ=None): + """ + Authentication function used for access control, + It tries to authenticate based on enabled authentication modules. + + :param username: username can be empty for container auth + :param password: password can be empty for container auth + :param environ: environ headers passed for container auth + :returns: None if auth failed, plugin_user dict if auth is correct + """ + + auth_plugins = RhodeCodeSetting.get_auth_plugins() + log.debug('Authentication against %s plugins' % (auth_plugins,)) + for module in auth_plugins: + try: + plugin = loadplugin(module) + except (ImportError, AttributeError, TypeError), e: + raise ImportError('Failed to load authentication module %s : %s' + % (module, str(e))) + log.debug('Trying authentication using ** %s **' % (module,)) + # load plugin settings from RhodeCode database + plugin_name = plugin.name + plugin_settings = {} + for v in plugin.plugin_settings(): + conf_key = "auth_%s_%s" % (plugin_name, v["name"]) + setting = RhodeCodeSetting.get_by_name(conf_key) + plugin_settings[v["name"]] = setting.app_settings_value if setting else None + log.debug('Plugin settings \n%s' % formatted_json(plugin_settings)) + + if not str2bool(plugin_settings["enabled"]): + log.info("Authentication plugin %s is disabled, skipping for %s" + % (module, username)) + continue + + # use plugin's method of user extraction. + user = plugin.get_user(username, environ=environ, + settings=plugin_settings) + log.debug('Plugin %s extracted user is `%s`' % (module, user)) + if not plugin.accepts(user): + log.debug('Plugin %s does not accept user `%s` for authentication' + % (module, user)) + continue + else: + log.debug('Plugin %s accepted user `%s` for authentication' + % (module, user)) + + log.info('Authenticating user using %s plugin' % plugin.__module__) + # _authenticate is a wrapper for .auth() method of plugin. + # it checks if .auth() sends proper data. for RhodeCodeExternalAuthPlugin + # it also maps users to Database and maps the attributes returned + # from .auth() to RhodeCode database. If this function returns data + # then auth is correct. + plugin_user = plugin._authenticate(user, username, password, + plugin_settings, + environ=environ or {}) + log.debug('PLUGIN USER DATA: %s' % plugin_user) + + if plugin_user: + log.debug('Plugin returned proper authentication data') + return plugin_user + + # we failed to Auth because .auth() method didn't return proper the user + log.warning("User `%s` failed to authenticate against %s" + % (username, plugin.__module__)) + return None diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth_modules/auth_container.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/auth_modules/auth_container.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,192 @@ +# -*- 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 . +""" +rhodecode.lib.auth_modules.auth_container +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +RhodeCode container based authentication plugin + +:created_on: Created on Nov 17, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import logging +from rhodecode.lib import auth_modules +from rhodecode.lib.utils2 import str2bool, safe_unicode +from rhodecode.lib.compat import hybrid_property +from rhodecode.model.db import User + +log = logging.getLogger(__name__) + + +class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin): + def __init__(self): + pass + + @hybrid_property + def name(self): + return "container" + + @hybrid_property + def is_container_auth(self): + return True + + def settings(self): + + settings = [ + { + "name": "header", + "validator": self.validators.UnicodeString(strip=True, not_empty=True), + "type": "string", + "description": "Header to extract the user from", + "default": "REMOTE_USER", + "formname": "Header" + }, + { + "name": "fallback_header", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Header to extract the user from when main one fails", + "default": "HTTP_X_FORWARDED_USER", + "formname": "Fallback header" + }, + { + "name": "clean_username", + "validator": self.validators.StringBoolean(if_missing=False), + "type": "bool", + "description": "Perform cleaning of user, if passed user has @ in username " + "then first part before @ is taken. " + "If there's \\ in the username only the part after \\ is taken", + "default": "True", + "formname": "Clean username" + }, + ] + return settings + + def use_fake_password(self): + return True + + def user_activation_state(self): + def_user_perms = User.get_default_user().AuthUser.permissions['global'] + return 'hg.extern_activate.auto' in def_user_perms + + def _clean_username(self, username): + # Removing realm and domain from username + username = username.partition('@')[0] + username = username.rpartition('\\')[2] + return username + + def _get_username(self, environ, settings): + username = None + environ = environ or {} + if not environ: + log.debug('got empty environ: %s' % environ) + + settings = settings or {} + if settings.get('header'): + header = settings.get('header') + username = environ.get(header) + log.debug('extracted %s:%s' % (header, username)) + + # fallback mode + if not username and settings.get('fallback_header'): + header = settings.get('fallback_header') + username = environ.get(header) + log.debug('extracted %s:%s' % (header, username)) + + if username and str2bool(settings.get('clean_username')): + log.debug('Received username %s from container' % username) + username = self._clean_username(username) + log.debug('New cleanup user is: %s' % username) + return username + + def get_user(self, username=None, **kwargs): + """ + Helper method for user fetching in plugins, by default it's using + simple fetch by username, but this method can be custimized in plugins + eg. container auth plugin to fetch user by environ params + :param username: username if given to fetch + :param kwargs: extra arguments needed for user fetching. + """ + environ = kwargs.get('environ') or {} + settings = kwargs.get('settings') or {} + username = self._get_username(environ, settings) + # we got the username, so use default method now + return super(RhodeCodeAuthPlugin, self).get_user(username) + + def auth(self, userobj, username, password, settings, **kwargs): + """ + Get's the container_auth username (or email). It tries to get username + from REMOTE_USER if this plugin is enabled, if that fails + it tries to get username from HTTP_X_FORWARDED_USER if fallback header + is set. clean_username extracts the username from this data if it's + having @ in it. + Return None on failure. On success, return a dictionary of the form: + + see: RhodeCodeAuthPluginBase.auth_func_attrs + + :param userobj: + :param username: + :param password: + :param settings: + :param kwargs: + """ + environ = kwargs.get('environ') + if not environ: + log.debug('Empty environ data skipping...') + return None + + if not userobj: + userobj = self.get_user('', environ=environ, settings=settings) + + # we don't care passed username/password for container auth plugins. + # only way to log in is using environ + username = None + if userobj: + username = getattr(userobj, 'username') + + if not username: + # we don't have any objects in DB user doesn't exist extrac username + # from environ based on the settings + username = self._get_username(environ, settings) + + # if cannot fetch username, it's a no-go for this plugin to proceed + if not username: + return None + + # old attrs fetched from RhodeCode database + admin = getattr(userobj, 'admin', False) + active = getattr(userobj, 'active', True) + email = getattr(userobj, 'email', '') + firstname = getattr(userobj, 'firstname', '') + lastname = getattr(userobj, 'lastname', '') + extern_type = getattr(userobj, 'extern_type', '') + + user_attrs = { + 'username': username, + 'firstname': safe_unicode(firstname or username), + 'lastname': safe_unicode(lastname or ''), + 'groups': [], + 'email': email or '', + 'admin': admin or False, + 'active': active, + 'active_from_extern': True, + 'extern_name': username, + 'extern_type': extern_type, + } + + log.info('user `%s` authenticated correctly' % user_attrs['username']) + return user_attrs diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth_modules/auth_crowd.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/auth_modules/auth_crowd.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,242 @@ +# -*- 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 . +""" +rhodecode.lib.auth_modules.auth_crowd +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +RhodeCode authentication plugin for Atlassian CROWD + +:created_on: Created on Nov 17, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + + +import base64 +import logging +import urllib2 +from rhodecode.lib import auth_modules +from rhodecode.lib.compat import json, formatted_json, hybrid_property +from rhodecode.model.db import User + +log = logging.getLogger(__name__) + + +class CrowdServer(object): + def __init__(self, *args, **kwargs): + """ + Create a new CrowdServer object that points to IP/Address 'host', + on the given port, and using the given method (https/http). user and + passwd can be set here or with set_credentials. If unspecified, + "version" defaults to "latest". + + example:: + + cserver = CrowdServer(host="127.0.0.1", + port="8095", + user="some_app", + passwd="some_passwd", + version="1") + """ + if not "port" in kwargs: + kwargs["port"] = "8095" + self._logger = kwargs.get("logger", logging.getLogger(__name__)) + self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"), + kwargs.get("host", "127.0.0.1"), + kwargs.get("port", "8095")) + self.set_credentials(kwargs.get("user", ""), + kwargs.get("passwd", "")) + self._version = kwargs.get("version", "latest") + self._url_list = None + self._appname = "crowd" + + def set_credentials(self, user, passwd): + self.user = user + self.passwd = passwd + self._make_opener() + + def _make_opener(self): + mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() + mgr.add_password(None, self._uri, self.user, self.passwd) + handler = urllib2.HTTPBasicAuthHandler(mgr) + self.opener = urllib2.build_opener(handler) + + def _request(self, url, body=None, headers=None, + method=None, noformat=False, + empty_response_ok=False): + _headers = {"Content-type": "application/json", + "Accept": "application/json"} + if self.user and self.passwd: + authstring = base64.b64encode("%s:%s" % (self.user, self.passwd)) + _headers["Authorization"] = "Basic %s" % authstring + if headers: + _headers.update(headers) + log.debug("Sent crowd: \n%s" + % (formatted_json({"url": url, "body": body, + "headers": _headers}))) + request = urllib2.Request(url, body, _headers) + if method: + request.get_method = lambda: method + + global msg + msg = "" + try: + rdoc = self.opener.open(request) + msg = "".join(rdoc.readlines()) + if not msg and empty_response_ok: + rval = {} + rval["status"] = True + rval["error"] = "Response body was empty" + elif not noformat: + rval = json.loads(msg) + rval["status"] = True + else: + rval = "".join(rdoc.readlines()) + except Exception, e: + if not noformat: + rval = {"status": False, + "body": body, + "error": str(e) + "\n" + msg} + else: + rval = None + return rval + + def user_auth(self, username, password): + """Authenticate a user against crowd. Returns brief information about + the user.""" + url = ("%s/rest/usermanagement/%s/authentication?username=%s" + % (self._uri, self._version, username)) + body = json.dumps({"value": password}) + return self._request(url, body) + + def user_groups(self, username): + """Retrieve a list of groups to which this user belongs.""" + url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s" + % (self._uri, self._version, username)) + return self._request(url) + + +class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin): + + @hybrid_property + def name(self): + return "crowd" + + def settings(self): + settings = [ + { + "name": "host", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "The FQDN or IP of the Atlassian CROWD Server", + "default": "127.0.0.1", + "formname": "Host" + }, + { + "name": "port", + "validator": self.validators.Number(strip=True), + "type": "int", + "description": "The Port in use by the Atlassian CROWD Server", + "default": 8095, + "formname": "Port" + }, + { + "name": "app_name", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "The Application Name to authenticate to CROWD", + "default": "", + "formname": "Application Name" + }, + { + "name": "app_password", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "The password to authenticate to CROWD", + "default": "", + "formname": "Application Password" + }, + { + "name": "admin_groups", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "A comma separated list of group names that identify users as RhodeCode Administrators", + "formname": "Admin Groups" + } + ] + return settings + + def use_fake_password(self): + return True + + def user_activation_state(self): + def_user_perms = User.get_default_user().AuthUser.permissions['global'] + return 'hg.extern_activate.auto' in def_user_perms + + def auth(self, userobj, username, password, settings, **kwargs): + """ + Given a user object (which may be null), username, a plaintext password, + and a settings object (containing all the keys needed as listed in settings()), + authenticate this user's login attempt. + + Return None on failure. On success, return a dictionary of the form: + + see: RhodeCodeAuthPluginBase.auth_func_attrs + This is later validated for correctness + """ + if not username or not password: + log.debug('Empty username or password skipping...') + return None + + log.debug("Crowd settings: \n%s" % (formatted_json(settings))) + server = CrowdServer(**settings) + server.set_credentials(settings["app_name"], settings["app_password"]) + crowd_user = server.user_auth(username, password) + log.debug("Crowd returned: \n%s" % (formatted_json(crowd_user))) + if not crowd_user["status"]: + return None + + res = server.user_groups(crowd_user["name"]) + log.debug("Crowd groups: \n%s" % (formatted_json(res))) + crowd_user["groups"] = [x["name"] for x in res["groups"]] + + # old attrs fetched from RhodeCode database + admin = getattr(userobj, 'admin', False) + active = getattr(userobj, 'active', True) + email = getattr(userobj, 'email', '') + firstname = getattr(userobj, 'firstname', '') + lastname = getattr(userobj, 'lastname', '') + extern_type = getattr(userobj, 'extern_type', '') + + user_attrs = { + 'username': username, + 'firstname': crowd_user["first-name"] or firstname, + 'lastname': crowd_user["last-name"] or lastname, + 'groups': crowd_user["groups"], + 'email': crowd_user["email"] or email, + 'admin': admin, + 'active': active, + 'active_from_extern': crowd_user.get('active'), + 'extern_name': crowd_user["name"], + 'extern_type': extern_type, + } + + # set an admin if we're in admin_groups of crowd + for group in settings["admin_groups"]: + if group in user_attrs["groups"]: + user_attrs["admin"] = True + log.debug("Final crowd user object: \n%s" % (formatted_json(user_attrs))) + log.info('user %s authenticated correctly' % user_attrs['username']) + return user_attrs diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth_modules/auth_ldap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/auth_modules/auth_ldap.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,361 @@ +# -*- 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 . +""" +rhodecode.lib.auth_modules.auth_ldap +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +RhodeCode authentication plugin for LDAP + +:created_on: Created on Nov 17, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + + +import logging +import traceback + +from rhodecode.lib import auth_modules +from rhodecode.lib.compat import hybrid_property +from rhodecode.lib.utils2 import safe_unicode, safe_str +from rhodecode.lib.exceptions import ( + LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError +) +from rhodecode.model.db import User + +log = logging.getLogger(__name__) + +try: + import ldap +except ImportError: + # means that python-ldap is not installed + ldap = None + + +class AuthLdap(object): + + def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', + tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, + ldap_filter='(&(objectClass=user)(!(objectClass=computer)))', + search_scope='SUBTREE', attr_login='uid'): + if ldap is None: + raise LdapImportError + + self.ldap_version = ldap_version + ldap_server_type = 'ldap' + + self.TLS_KIND = tls_kind + + if self.TLS_KIND == 'LDAPS': + port = port or 689 + ldap_server_type = ldap_server_type + 's' + + OPT_X_TLS_DEMAND = 2 + self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, + OPT_X_TLS_DEMAND) + # split server into list + self.LDAP_SERVER_ADDRESS = server.split(',') + self.LDAP_SERVER_PORT = port + + # USE FOR READ ONLY BIND TO LDAP SERVER + self.LDAP_BIND_DN = safe_str(bind_dn) + self.LDAP_BIND_PASS = safe_str(bind_pass) + _LDAP_SERVERS = [] + for host in self.LDAP_SERVER_ADDRESS: + _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type, + host.replace(' ', ''), + self.LDAP_SERVER_PORT)) + self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS)) + self.BASE_DN = safe_str(base_dn) + self.LDAP_FILTER = safe_str(ldap_filter) + self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) + self.attr_login = attr_login + + def authenticate_ldap(self, username, password): + """ + Authenticate a user via LDAP and return his/her LDAP properties. + + Raises AuthenticationError if the credentials are rejected, or + EnvironmentError if the LDAP server can't be reached. + + :param username: username + :param password: password + """ + + from rhodecode.lib.helpers import chop_at + + uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS) + + if not password: + log.debug("Attempt to authenticate LDAP user " + "with blank password rejected.") + raise LdapPasswordError() + if "," in username: + raise LdapUsernameError("invalid character in username: ,") + try: + if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, + '/etc/openldap/cacerts') + ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) + ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) + ldap.set_option(ldap.OPT_TIMEOUT, 20) + ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10) + ldap.set_option(ldap.OPT_TIMELIMIT, 15) + if self.TLS_KIND != 'PLAIN': + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT) + server = ldap.initialize(self.LDAP_SERVER) + if self.ldap_version == 2: + server.protocol = ldap.VERSION2 + else: + server.protocol = ldap.VERSION3 + + if self.TLS_KIND == 'START_TLS': + server.start_tls_s() + + if self.LDAP_BIND_DN and self.LDAP_BIND_PASS: + log.debug('Trying simple_bind with password and given DN: %s' + % self.LDAP_BIND_DN) + server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) + + filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, + username) + log.debug("Authenticating %r filter %s at %s", self.BASE_DN, + filter_, self.LDAP_SERVER) + lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, + filter_) + + if not lobjects: + raise ldap.NO_SUCH_OBJECT() + + for (dn, _attrs) in lobjects: + if dn is None: + continue + + try: + log.debug('Trying simple bind with %s' % dn) + server.simple_bind_s(dn, safe_str(password)) + attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, + '(objectClass=*)')[0][1] + break + + except ldap.INVALID_CREDENTIALS: + log.debug("LDAP rejected password for user '%s' (%s): %s" + % (uid, username, dn)) + + else: + log.debug("No matching LDAP objects for authentication " + "of '%s' (%s)", uid, username) + raise LdapPasswordError() + + except ldap.NO_SUCH_OBJECT: + log.debug("LDAP says no such user '%s' (%s)" % (uid, username)) + raise LdapUsernameError() + except ldap.SERVER_DOWN: + raise LdapConnectionError("LDAP can't access authentication server") + + return dn, attrs + + +class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin): + def __init__(self): + self._logger = logging.getLogger(__name__) + self._tls_kind_values = ["PLAIN", "LDAPS", "START_TLS"] + self._tls_reqcert_values = ["NEVER", "ALLOW", "TRY", "DEMAND", "HARD"] + self._search_scopes = ["BASE", "ONELEVEL", "SUBTREE"] + + @hybrid_property + def name(self): + return "ldap" + + def settings(self): + settings = [ + { + "name": "host", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Host of the LDAP Server", + "formname": "LDAP Host" + }, + { + "name": "port", + "validator": self.validators.Number(strip=True, not_empty=True), + "type": "string", + "description": "Port that the LDAP server is listening on", + "default": 389, + "formname": "Port" + }, + { + "name": "dn_user", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "User to connect to LDAP", + "formname": "Account" + }, + { + "name": "dn_pass", + "validator": self.validators.UnicodeString(strip=True), + "type": "password", + "description": "Password to connect to LDAP", + "formname": "Password" + }, + { + "name": "tls_kind", + "validator": self.validators.OneOf(self._tls_kind_values), + "type": "select", + "values": self._tls_kind_values, + "description": "TLS Type", + "default": 'PLAIN', + "formname": "Connection Security" + }, + { + "name": "tls_reqcert", + "validator": self.validators.OneOf(self._tls_reqcert_values), + "type": "select", + "values": self._tls_reqcert_values, + "description": "Require Cert over TLS?", + "formname": "Certificate Checks" + }, + { + "name": "base_dn", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Base DN to search (e.g., dc=mydomain,dc=com)", + "formname": "Base DN" + }, + { + "name": "filter", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Filter to narrow results (e.g., ou=Users, etc)", + "formname": "LDAP Search Filter" + }, + { + "name": "search_scope", + "validator": self.validators.OneOf(self._search_scopes), + "type": "select", + "values": self._search_scopes, + "description": "How deep to search LDAP", + "formname": "LDAP Search Scope" + }, + { + "name": "attr_login", + "validator": self.validators.AttrLoginValidator(not_empty=True, strip=True), + "type": "string", + "description": "LDAP Attribute to map to user name", + "formname": "Login Attribute" + }, + { + "name": "attr_firstname", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "LDAP Attribute to map to first name", + "formname": "First Name Attribute" + }, + { + "name": "attr_lastname", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "LDAP Attribute to map to last name", + "formname": "Last Name Attribute" + }, + { + "name": "attr_email", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "LDAP Attribute to map to email address", + "formname": "Email Attribute" + } + ] + return settings + + def use_fake_password(self): + return True + + def user_activation_state(self): + def_user_perms = User.get_default_user().AuthUser.permissions['global'] + return 'hg.extern_activate.auto' in def_user_perms + + def auth(self, userobj, username, password, settings, **kwargs): + """ + Given a user object (which may be null), username, a plaintext password, + and a settings object (containing all the keys needed as listed in settings()), + authenticate this user's login attempt. + + Return None on failure. On success, return a dictionary of the form: + + see: RhodeCodeAuthPluginBase.auth_func_attrs + This is later validated for correctness + """ + + if not username or not password: + log.debug('Empty username or password skipping...') + return None + + kwargs = { + 'server': settings.get('host', ''), + 'base_dn': settings.get('base_dn', ''), + 'port': settings.get('port'), + 'bind_dn': settings.get('dn_user'), + 'bind_pass': settings.get('dn_pass'), + 'tls_kind': settings.get('tls_kind'), + 'tls_reqcert': settings.get('tls_reqcert'), + 'ldap_filter': settings.get('filter'), + 'search_scope': settings.get('search_scope'), + 'attr_login': settings.get('attr_login'), + 'ldap_version': 3, + } + + if kwargs['bind_dn'] and not kwargs['bind_pass']: + log.debug('Using dynamic binding.') + kwargs['bind_dn'] = kwargs['bind_dn'].replace('$login', username) + kwargs['bind_pass'] = password + log.debug('Checking for ldap authentication') + + try: + aldap = AuthLdap(**kwargs) + (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password) + log.debug('Got ldap DN response %s' % user_dn) + + get_ldap_attr = lambda k: ldap_attrs.get(settings.get(k), [''])[0] + + # old attrs fetched from RhodeCode database + admin = getattr(userobj, 'admin', False) + active = getattr(userobj, 'active', True) + email = getattr(userobj, 'email', '') + firstname = getattr(userobj, 'firstname', '') + lastname = getattr(userobj, 'lastname', '') + extern_type = getattr(userobj, 'extern_type', '') + + user_attrs = { + 'username': username, + 'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname), + 'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname), + 'groups': [], + 'email': get_ldap_attr('attr_email' or email), + 'admin': admin, + 'active': active, + "active_from_extern": None, + 'extern_name': user_dn, + 'extern_type': extern_type, + } + log.info('user %s authenticated correctly' % user_attrs['username']) + return user_attrs + + except (LdapUsernameError, LdapPasswordError, LdapImportError): + log.error(traceback.format_exc()) + return None + except (Exception,): + log.error(traceback.format_exc()) + return None diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth_modules/auth_pam.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/auth_modules/auth_pam.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,138 @@ +# -*- 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 . +""" +rhodecode.lib.auth_pam +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +RhodeCode authentication library for PAM + +:created_on: Created on Apr 09, 2013 +:author: Alexey Larikov +""" + +import logging +import time +import pam +import pwd +import grp +import re +import socket +import threading + +from rhodecode.lib import auth_modules +from rhodecode.lib.compat import formatted_json, hybrid_property + +log = logging.getLogger(__name__) + +# Cache to store PAM authenticated users +_auth_cache = dict() +_pam_lock = threading.Lock() + + +class RhodeCodeAuthPlugin(auth_modules.RhodeCodeExternalAuthPlugin): + # PAM authnetication can be slow. Repository operations involve a lot of + # auth calls. Little caching helps speedup push/pull operations significantly + AUTH_CACHE_TTL = 4 + + def __init__(self): + global _auth_cache + ts = time.time() + cleared_cache = dict( + [(k, v) for (k, v) in _auth_cache.items() if + (v + RhodeCodeAuthPlugin.AUTH_CACHE_TTL > ts)]) + _auth_cache = cleared_cache + + @hybrid_property + def name(self): + return "pam" + + def settings(self): + settings = [ + { + "name": "service", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "PAM service name to use for authentication", + "default": "login", + "formname": "PAM service name" + }, + { + "name": "gecos", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Regex for extracting user name/email etc " + "from Unix userinfo", + "default": "(?P.+),\s*(?P\w+)", + "formname": "Gecos Regex" + } + ] + return settings + + def use_fake_password(self): + return True + + def auth(self, userobj, username, password, settings, **kwargs): + if username not in _auth_cache: + # Need lock here, as PAM authentication is not thread safe + _pam_lock.acquire() + try: + auth_result = pam.authenticate(username, password, + settings["service"]) + # cache result only if we properly authenticated + if auth_result: + _auth_cache[username] = time.time() + finally: + _pam_lock.release() + + if not auth_result: + log.error("PAM was unable to authenticate user: %s" % (username,)) + return None + else: + log.debug("Using cached auth for user: %s" % (username,)) + + # old attrs fetched from RhodeCode database + admin = getattr(userobj, 'admin', False) + active = getattr(userobj, 'active', True) + email = getattr(userobj, 'email', '') or "%s@%s" % (username, socket.gethostname()) + firstname = getattr(userobj, 'firstname', '') + lastname = getattr(userobj, 'lastname', '') + extern_type = getattr(userobj, 'extern_type', '') + + user_attrs = { + 'username': username, + 'firstname': firstname, + 'lastname': lastname, + 'groups': [g.gr_name for g in grp.getgrall() if username in g.gr_mem], + 'email': email, + 'admin': admin, + 'active': active, + "active_from_extern": None, + 'extern_name': username, + 'extern_type': extern_type, + } + + try: + user_data = pwd.getpwnam(username) + regex = settings["gecos"] + match = re.search(regex, user_data.pw_gecos) + if match: + user_attrs["firstname"] = match.group('first_name') + user_attrs["lastname"] = match.group('last_name') + except Exception: + log.warn("Cannot extract additional info for PAM user") + pass + + log.debug("pamuser: \n%s" % formatted_json(user_attrs)) + log.info('user %s authenticated correctly' % user_attrs['username']) + return user_attrs diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/auth_modules/auth_rhodecode.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/auth_modules/auth_rhodecode.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,97 @@ +# -*- 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 . +""" +rhodecode.lib.auth_modules.auth_rhodecode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +RhodeCode authentication plugin for built in internal auth + +:created_on: Created on Nov 17, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + + +import logging +from rhodecode.lib import auth_modules +from rhodecode.lib.compat import formatted_json, hybrid_property +from rhodecode.model.db import User + + +log = logging.getLogger(__name__) + + +class RhodeCodeAuthPlugin(auth_modules.RhodeCodeAuthPluginBase): + def __init__(self): + pass + + @hybrid_property + def name(self): + return "rhodecode" + + def settings(self): + return [] + + def user_activation_state(self): + def_user_perms = User.get_default_user().AuthUser.permissions['global'] + return 'hg.register.auto_activate' in def_user_perms + + def accepts(self, user, accepts_empty=True): + """ + Custom accepts for this auth that doesn't accept empty users. We + know that user exisits in database. + """ + return super(RhodeCodeAuthPlugin, self).accepts(user, + accepts_empty=False) + + def auth(self, userobj, username, password, settings, **kwargs): + if not userobj: + log.debug('userobj was:%s skipping' % (userobj, )) + return None + if userobj.extern_type != self.name: + log.warn("userobj:%s extern_type mismatch got:`%s` expected:`%s`" + % (userobj, userobj.extern_type, self.name)) + return None + + user_attrs = { + "username": userobj.username, + "firstname": userobj.firstname, + "lastname": userobj.lastname, + "groups": [], + "email": userobj.email, + "admin": userobj.admin, + "active": userobj.active, + "active_from_extern": userobj.active, + "extern_name": userobj.user_id, + 'extern_type': userobj.extern_type, + } + + log.debug(formatted_json(user_attrs)) + if userobj.active: + from rhodecode.lib import auth + password_match = auth.RhodeCodeCrypto.hash_check(password, userobj.password) + if userobj.username == User.DEFAULT_USER and userobj.active: + log.info('user %s authenticated correctly as anonymous user' % + username) + return user_attrs + + elif userobj.username == username and password_match: + log.info('user %s authenticated correctly' % user_attrs['username']) + return user_attrs + log.error("user %s had a bad password" % username) + return None + else: + log.warning('user %s tried auth but is disabled' % username) + return None diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/base.py --- a/rhodecode/lib/base.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/base.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,7 +1,31 @@ -"""The base Controller API +# -*- 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 . -Provides the BaseController class for subclassing. """ +rhodecode.lib.base +~~~~~~~~~~~~~~~~~~ + +The base Controller API +Provides the BaseController class for subclassing. And usage in different +controllers + +:created_on: Oct 06, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import time import traceback @@ -13,14 +37,14 @@ from pylons import config, tmpl_context as c, request, session, url from pylons.controllers import WSGIController from pylons.controllers.util import redirect -from pylons.templating import render_mako as render +from pylons.templating import render_mako as render # don't remove this import from rhodecode import __version__, BACKENDS from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\ safe_str, safe_int -from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\ - HasPermissionAnyMiddleware, CookieStoreWrapper +from rhodecode.lib import auth_modules +from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware, CookieStoreWrapper from rhodecode.lib.utils import get_repo_slug from rhodecode.lib.exceptions import UserCreationError from rhodecode.model import meta @@ -100,7 +124,7 @@ _parts = auth.split(':', 1) if len(_parts) == 2: username, password = _parts - if self.authfunc(environ, username, password): + if self.authfunc(username, password, environ): return username return self.build_authentication() @@ -114,8 +138,8 @@ self.config = config # base path of repo locations self.basepath = self.config['base_path'] - #authenticate this mercurial request using authfunc - self.authenticate = BasicAuth('', authfunc, + #authenticate this VCS request using authfunc + self.authenticate = BasicAuth('', auth_modules.authenticate, config.get('auth_ret_code')) self.ip_addr = '0.0.0.0' @@ -129,18 +153,13 @@ :param repo_name: """ - try: - data = repo_name.split('/') - if len(data) >= 2: - by_id = data[1].split('_') - if len(by_id) == 2 and by_id[1].isdigit(): - _repo_name = Repository.get(by_id[1]).repo_name - data[1] = _repo_name - except Exception: - log.debug('Failed to extract repo_name from id %s' % ( - traceback.format_exc() - ) - ) + + data = repo_name.split('/') + if len(data) >= 2: + from rhodecode.lib.utils import get_repo_by_id + by_id_match = get_repo_by_id(repo_name) + if by_id_match: + data[1] = by_id_match return '/'.join(data) @@ -161,12 +180,15 @@ :param user: user instance :param repo_name: repository name """ - #check IP - authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr) - if not authuser.ip_allowed: + # check IP + inherit = user.inherit_default_permissions + ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr, + inherit_from_default=inherit) + if ip_allowed: + log.info('Access for IP:%s allowed' % (ip_addr,)) + else: return False - else: - log.info('Access for IP:%s allowed' % (ip_addr)) + if action == 'push': if not HasPermissionAnyMiddleware('repository.write', 'repository.admin')(user, @@ -261,31 +283,40 @@ __before__ is called before controller methods and after __call__ """ c.rhodecode_version = __version__ - c.rhodecode_instanceid = config.get('instance_id') - c.rhodecode_name = config.get('rhodecode_title') - c.rhodecode_bugtracker = config.get('bugtracker', 'http://bitbucket.org/marcinkuzminski/rhodecode/issues') - c.use_gravatar = str2bool(config.get('use_gravatar')) - c.ga_code = config.get('rhodecode_ga_code') + rc_config = RhodeCodeSetting.get_app_settings() + # Visual options c.visual = AttributeDict({}) - rc_config = RhodeCodeSetting.get_app_settings() + ## DB stored c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon')) c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon')) c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags')) c.visual.dashboard_items = safe_int(rc_config.get('rhodecode_dashboard_items', 100)) + c.visual.admin_grid_items = safe_int(rc_config.get('rhodecode_admin_grid_items', 100)) c.visual.repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) c.visual.show_version = str2bool(rc_config.get('rhodecode_show_version')) + c.visual.use_gravatar = str2bool(rc_config.get('rhodecode_use_gravatar')) + c.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url') + + c.ga_code = rc_config.get('rhodecode_ga_code') + c.rhodecode_name = rc_config.get('rhodecode_title') + c.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl') ## INI stored - self.cut_off_limit = int(config.get('cut_off_limit')) c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True)) c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True)) + c.rhodecode_instanceid = config.get('instance_id') + c.rhodecode_bugtracker = config.get('bugtracker', url('rc_issue_tracker')) + # END CONFIG VARS + c.repo_name = get_repo_slug(request) # can be empty c.backends = BACKENDS.keys() c.unread_notifications = NotificationModel()\ .get_unread_cnt_for_user(c.rhodecode_user.user_id) + + self.cut_off_limit = safe_int(config.get('cut_off_limit')) self.sa = meta.Session self.scm_model = ScmModel(self.sa) @@ -298,27 +329,33 @@ self.ip_addr = _get_ip_addr(environ) # make sure that we update permissions each time we call controller api_key = request.GET.get('api_key') - cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) - user_id = cookie_store.get('user_id', None) - username = get_container_username(environ, config) - try: - auth_user = AuthUser(user_id, api_key, username, self.ip_addr) - except UserCreationError, e: - from rhodecode.lib import helpers as h - h.flash(e, 'error') - # container auth or other auth functions that create users on - # the fly can throw this exception signaling that there's issue - # with user creation, explanation should be provided in - # Exception itself - auth_user = AuthUser(ip_addr=self.ip_addr) + if api_key: + # when using API_KEY we are sure user exists. + auth_user = AuthUser(api_key=api_key, ip_addr=self.ip_addr) + authenticated = False + else: + cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) + try: + auth_user = AuthUser(user_id=cookie_store.get('user_id', None), + ip_addr=self.ip_addr) + except UserCreationError, e: + from rhodecode.lib import helpers as h + h.flash(e, 'error') + # container auth or other auth functions that create users on + # the fly can throw this exception signaling that there's issue + # with user creation, explanation should be provided in + # Exception itself + auth_user = AuthUser(ip_addr=self.ip_addr) + + authenticated = cookie_store.get('is_authenticated') + + if not auth_user.is_authenticated and auth_user.user_id is not None: + # user is not authenticated and not empty + auth_user.set_authenticated(authenticated) request.user = auth_user + #set globals for auth user self.rhodecode_user = c.rhodecode_user = auth_user - if not self.rhodecode_user.is_authenticated and \ - self.rhodecode_user.user_id is not None: - self.rhodecode_user.set_authenticated( - cookie_store.get('is_authenticated') - ) log.info('IP: %s User: %s accessed %s' % ( self.ip_addr, auth_user, safe_unicode(_get_access_path(environ))) ) @@ -341,21 +378,39 @@ def __before__(self): super(BaseRepoController, self).__before__() - if c.repo_name: + if c.repo_name: # extracted from routes + _dbr = Repository.get_by_repo_name(c.repo_name) + if not _dbr: + return + + log.debug('Found repository in database %s with state `%s`' + % (safe_unicode(_dbr), safe_unicode(_dbr.repo_state))) + route = getattr(request.environ.get('routes.route'), 'name', '') - dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) + # allow to delete repos that are somehow damages in filesystem + if route in ['delete_repo']: + return + + if _dbr.repo_state in [Repository.STATE_PENDING]: + if route in ['repo_creating_home']: + return + check_url = url('repo_creating_home', repo_name=c.repo_name) + return redirect(check_url) + + dbr = c.rhodecode_db_repo = _dbr c.rhodecode_repo = c.rhodecode_db_repo.scm_instance - # update last change according to VCS data - dbr.update_changeset_cache(dbr.get_changeset()) if c.rhodecode_repo is None: log.error('%s this repository is present in database but it ' 'cannot be created as an scm instance', c.repo_name) redirect(url('home')) + # update last change according to VCS data + dbr.update_changeset_cache(dbr.get_changeset()) + # some globals counter for menu c.repository_followers = self.scm_model.get_followers(dbr) c.repository_forks = self.scm_model.get_forks(dbr) c.repository_pull_requests = self.scm_model.get_pull_requests(dbr) - c.repository_following = self.scm_model.is_following_repo(c.repo_name, - self.rhodecode_user.user_id) + c.repository_following = self.scm_model.is_following_repo( + c.repo_name, self.rhodecode_user.user_id) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/celerylib/__init__.py --- a/rhodecode/lib/celerylib/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/celerylib/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.celerylib.__init__ - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - celery libs for RhodeCode - - :created_on: Nov 27, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.celerylib.__init__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +celery libs for RhodeCode + +:created_on: Nov 27, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os import sys diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/celerylib/tasks.py --- a/rhodecode/lib/celerylib/tasks.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/celerylib/tasks.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.celerylib.tasks - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - RhodeCode task modules, containing all task that suppose to be run - by celery daemon - - :created_on: Oct 6, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.celerylib.tasks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +RhodeCode task modules, containing all task that suppose to be run +by celery daemon + +:created_on: Oct 6, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from celery.decorators import task import os @@ -53,7 +54,7 @@ from rhodecode.model.scm import ScmModel -add_cache(config) +add_cache(config) # pragma: no cover __all__ = ['whoosh_index', 'get_commits_stats', 'reset_user_password', 'send_email'] @@ -63,7 +64,7 @@ if CELERY_ON: try: log = cls.get_logger() - except: + except Exception: log = logging.getLogger(__name__) else: log = logging.getLogger(__name__) @@ -299,8 +300,96 @@ return False return True +@task(ignore_result=False) +@dbsession +def create_repo(form_data, cur_user): + from rhodecode.model.repo import RepoModel + from rhodecode.model.user import UserModel + from rhodecode.model.db import RhodeCodeSetting -@task(ignore_result=True) + log = get_logger(create_repo) + DBS = get_session() + + cur_user = UserModel(DBS)._get_user(cur_user) + + owner = cur_user + repo_name = form_data['repo_name'] + repo_name_full = form_data['repo_name_full'] + repo_type = form_data['repo_type'] + description = form_data['repo_description'] + private = form_data['repo_private'] + clone_uri = form_data.get('clone_uri') + repo_group = form_data['repo_group'] + landing_rev = form_data['repo_landing_rev'] + copy_fork_permissions = form_data.get('copy_permissions') + copy_group_permissions = form_data.get('repo_copy_permissions') + fork_of = form_data.get('fork_parent_id') + state = form_data.get('repo_state', Repository.STATE_PENDING) + + # repo creation defaults, private and repo_type are filled in form + defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) + enable_statistics = defs.get('repo_enable_statistics') + enable_locking = defs.get('repo_enable_locking') + enable_downloads = defs.get('repo_enable_downloads') + + try: + repo = RepoModel(DBS)._create_repo( + repo_name=repo_name_full, + repo_type=repo_type, + description=description, + owner=owner, + private=private, + clone_uri=clone_uri, + repo_group=repo_group, + landing_rev=landing_rev, + fork_of=fork_of, + copy_fork_permissions=copy_fork_permissions, + copy_group_permissions=copy_group_permissions, + enable_statistics=enable_statistics, + enable_locking=enable_locking, + enable_downloads=enable_downloads, + state=state + ) + + action_logger(cur_user, 'user_created_repo', + form_data['repo_name_full'], '', DBS) + + DBS.commit() + # now create this repo on Filesystem + RepoModel(DBS)._create_filesystem_repo( + repo_name=repo_name, + repo_type=repo_type, + repo_group=RepoModel(DBS)._get_repo_group(repo_group), + clone_uri=clone_uri, + ) + repo = Repository.get_by_repo_name(repo_name_full) + log_create_repository(repo.get_dict(), created_by=owner.username) + + # update repo changeset caches initially + repo.update_changeset_cache() + + # set new created state + repo.set_state(Repository.STATE_CREATED) + DBS.commit() + except Exception, e: + log.warning('Exception %s occured when forking repository, ' + 'doing cleanup...' % e) + # rollback things manually ! + repo = Repository.get_by_repo_name(repo_name_full) + if repo: + Repository.delete(repo.repo_id) + DBS.commit() + RepoModel(DBS)._delete_filesystem_repo(repo) + raise + + # it's an odd fix to make celery fail task when exception occurs + def on_failure(self, *args, **kwargs): + pass + + return True + + +@task(ignore_result=False) @dbsession def create_repo_fork(form_data, cur_user): """ @@ -318,60 +407,75 @@ base_path = Repository.base_path() cur_user = UserModel(DBS)._get_user(cur_user) - fork_name = form_data['repo_name_full'] + repo_name = form_data['repo_name'] # fork in this case + repo_name_full = form_data['repo_name_full'] + repo_type = form_data['repo_type'] - description = form_data['description'] owner = cur_user private = form_data['private'] clone_uri = form_data.get('clone_uri') - repos_group = form_data['repo_group'] + repo_group = form_data['repo_group'] landing_rev = form_data['landing_rev'] copy_fork_permissions = form_data.get('copy_permissions') - fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id')) - fork_repo = RepoModel(DBS).create_repo( - fork_name, repo_type, description, owner, private, clone_uri, - repos_group, landing_rev, just_db=True, fork_of=fork_of, - copy_fork_permissions=copy_fork_permissions - ) + try: + fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id')) - update_after_clone = form_data['update_after_clone'] + fork_repo = RepoModel(DBS)._create_repo( + repo_name=repo_name_full, + repo_type=repo_type, + description=form_data['description'], + owner=owner, + private=private, + clone_uri=clone_uri, + repo_group=repo_group, + landing_rev=landing_rev, + fork_of=fork_of, + copy_fork_permissions=copy_fork_permissions + ) + action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full, + fork_of.repo_name, '', DBS) + DBS.commit() - source_repo_path = os.path.join(base_path, fork_of.repo_name) - destination_fork_path = os.path.join(base_path, fork_name) - - log.info('creating fork of %s as %s', source_repo_path, - destination_fork_path) - backend = get_backend(repo_type) + update_after_clone = form_data['update_after_clone'] + source_repo_path = os.path.join(base_path, fork_of.repo_name) - if repo_type == 'git': - r = backend(safe_str(destination_fork_path), create=True, - src_url=safe_str(source_repo_path), - update_after_clone=update_after_clone, - bare=True) - # add rhodecode hook into this repo - ScmModel().install_git_hook(repo=r) - elif repo_type == 'hg': - r = backend(safe_str(destination_fork_path), create=True, - src_url=safe_str(source_repo_path), - update_after_clone=update_after_clone) - else: - raise Exception('Unknown backend type %s' % repo_type) + # now create this repo on Filesystem + RepoModel(DBS)._create_filesystem_repo( + repo_name=repo_name, + repo_type=repo_type, + repo_group=RepoModel(DBS)._get_repo_group(repo_group), + clone_uri=source_repo_path, + ) + repo = Repository.get_by_repo_name(repo_name_full) + log_create_repository(repo.get_dict(), created_by=owner.username) + + # update repo changeset caches initially + repo.update_changeset_cache() - log_create_repository(fork_repo.get_dict(), created_by=cur_user.username) - - action_logger(cur_user, 'user_forked_repo:%s' % fork_name, - fork_of.repo_name, '', DBS) + # set new created state + repo.set_state(Repository.STATE_CREATED) + DBS.commit() + except Exception, e: + log.warning('Exception %s occured when forking repository, ' + 'doing cleanup...' % e) + #rollback things manually ! + repo = Repository.get_by_repo_name(repo_name_full) + if repo: + Repository.delete(repo.repo_id) + DBS.commit() + RepoModel(DBS)._delete_filesystem_repo(repo) + raise - action_logger(cur_user, 'user_created_fork:%s' % fork_name, - fork_name, '', DBS) - # finally commit at latest possible stage - DBS.commit() - fork_repo.update_changeset_cache() + # it's an odd fix to make celery fail task when exception occurs + def on_failure(self, *args, **kwargs): + pass + + return True def __get_codes_stats(repo_name): - from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP + from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP repo = Repository.get_by_repo_name(repo_name).scm_instance tip = repo.get_changeset() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/celerypylons/__init__.py --- a/rhodecode/lib/celerypylons/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/celerypylons/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + """ Automatically sets the environment variable `CELERY_LOADER` to `celerypylons.loader:PylonsLoader`. This ensures the loader is @@ -7,6 +9,7 @@ import celerypylons """ + import os import warnings diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/celerypylons/commands.py --- a/rhodecode/lib/celerypylons/commands.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/celerypylons/commands.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import rhodecode from rhodecode.lib.utils import BasePasterCommand, Command, load_rcextensions from celery.app import app_or_default @@ -36,7 +38,7 @@ CELERY_ON = False if not CELERY_ON: - raise Exception('Please enable celery_on in .ini config ' + raise Exception('Please set use_celery = true in .ini config ' 'file before running celeryd') rhodecode.CELERY_ON = CELERY_ON load_rcextensions(config['here']) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/celerypylons/loader.py --- a/rhodecode/lib/celerypylons/loader.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/celerypylons/loader.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from celery.loaders.base import BaseLoader from pylons import config diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/colored_formatter.py --- a/rhodecode/lib/colored_formatter.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/colored_formatter.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,3 +1,16 @@ +# -*- 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 . import logging diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/compat.py --- a/rhodecode/lib/compat.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/compat.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.compat - ~~~~~~~~~~~~~~~~~~~~ - - Python backward compatibility functions and common libs - - - :created_on: Oct 7, 2011 - :author: marcink - :copyright: (C) 2010-2010 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,8 +11,23 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.compat +~~~~~~~~~~~~~~~~~~~~ + +Python backward compatibility functions and common libs + + +:created_on: Oct 7, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os +import functools +import importlib from rhodecode import __py_version__, is_windows #============================================================================== @@ -32,6 +35,9 @@ #============================================================================== from rhodecode.lib.ext_json import json +# alias for formatted json +formatted_json = functools.partial(json.dumps, indent=4, sort_keys=True) + if __py_version__ >= (2, 7): import unittest else: @@ -367,6 +373,12 @@ #============================================================================== +# Hybrid property/method +#============================================================================== +from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property + + +#============================================================================== # kill FUNCTIONS #============================================================================== if is_windows: diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/db_manage.py --- a/rhodecode/lib/db_manage.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/db_manage.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.db_manage - ~~~~~~~~~~~~~~~~~~~~~~~ - - Database creation, and setup module for RhodeCode. Used for creation - of database as well as for migration operations - - :created_on: Apr 10, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.db_manage +~~~~~~~~~~~~~~~~~~~~~~~ + +Database creation, and setup module for RhodeCode. Used for creation +of database as well as for migration operations + +:created_on: Apr 10, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import sys @@ -39,15 +39,15 @@ from rhodecode.model import init_model from rhodecode.model.db import User, Permission, RhodeCodeUi, \ RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \ - UserRepoGroupToPerm, CacheInvalidation, UserGroup + UserRepoGroupToPerm, CacheInvalidation, UserGroup, Repository from sqlalchemy.engine import create_engine -from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.model.repo_group import RepoGroupModel #from rhodecode.model import meta from rhodecode.model.meta import Session, Base from rhodecode.model.repo import RepoModel from rhodecode.model.permission import PermissionModel -from rhodecode.model.users_group import UserGroupModel +from rhodecode.model.user_group import UserGroupModel log = logging.getLogger(__name__) @@ -61,172 +61,8 @@ print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper() -class UpgradeSteps(object): - """ - Those steps follow schema versions so for example schema - for example schema with seq 002 == step_2 and so on. - """ - - def __init__(self, klass): - self.klass = klass - - def step_1(self): - pass - - def step_2(self): - notify('Patching repo paths for newer version of RhodeCode') - self.klass.fix_repo_paths() - - notify('Patching default user of RhodeCode') - self.klass.fix_default_user() - - log.info('Changing ui settings') - self.klass.create_ui_settings() - - def step_3(self): - notify('Adding additional settings into RhodeCode db') - self.klass.fix_settings() - notify('Adding ldap defaults') - self.klass.create_ldap_options(skip_existing=True) - - def step_4(self): - notify('create permissions and fix groups') - self.klass.create_permissions() - self.klass.fixup_groups() - - def step_5(self): - pass - - def step_6(self): - - notify('re-checking permissions') - self.klass.create_permissions() - - notify('installing new UI options') - sett4 = RhodeCodeSetting('show_public_icon', True) - Session().add(sett4) - sett5 = RhodeCodeSetting('show_private_icon', True) - Session().add(sett5) - sett6 = RhodeCodeSetting('stylify_metatags', False) - Session().add(sett6) - - notify('fixing old PULL hook') - _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger') - if _pull: - _pull.ui_key = RhodeCodeUi.HOOK_PULL - Session().add(_pull) - - notify('fixing old PUSH hook') - _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger') - if _push: - _push.ui_key = RhodeCodeUi.HOOK_PUSH - Session().add(_push) - - notify('installing new pre-push hook') - hooks4 = RhodeCodeUi() - hooks4.ui_section = 'hooks' - hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH - hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push' - Session().add(hooks4) - - notify('installing new pre-pull hook') - hooks6 = RhodeCodeUi() - hooks6.ui_section = 'hooks' - hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL - hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull' - Session().add(hooks6) - - notify('installing hgsubversion option') - # enable hgsubversion disabled by default - hgsubversion = RhodeCodeUi() - hgsubversion.ui_section = 'extensions' - hgsubversion.ui_key = 'hgsubversion' - hgsubversion.ui_value = '' - hgsubversion.ui_active = False - Session().add(hgsubversion) - - notify('installing hg git option') - # enable hggit disabled by default - hggit = RhodeCodeUi() - hggit.ui_section = 'extensions' - hggit.ui_key = 'hggit' - hggit.ui_value = '' - hggit.ui_active = False - Session().add(hggit) - - notify('re-check default permissions') - default_user = User.get_by_username(User.DEFAULT_USER) - perm = Permission.get_by_key('hg.fork.repository') - reg_perm = UserToPerm() - reg_perm.user = default_user - reg_perm.permission = perm - Session().add(reg_perm) - - def step_7(self): - perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER) - Session().commit() - if perm_fixes: - notify('There was an inconsistent state of permissions ' - 'detected for default user. Permissions are now ' - 'reset to the default value for default user. ' - 'Please validate and check default permissions ' - 'in admin panel') - - def step_8(self): - self.klass.create_permissions() - self.klass.populate_default_permissions() - self.klass.create_default_options(skip_existing=True) - Session().commit() - - def step_9(self): - pass - - def step_10(self): - pass - - def step_11(self): - self.klass.update_repo_info() - - def step_12(self): - self.klass.create_permissions() - Session().commit() - - self.klass.populate_default_permissions() - Session().commit() - - #fix all usergroups - ug_model = UserGroupModel() - for ug in UserGroup.get_all(): - perm_obj = ug_model._create_default_perms(ug) - Session().add(perm_obj) - Session().commit() - - adm = User.get_first_admin() - # fix owners of UserGroup - for ug in Session().query(UserGroup).all(): - ug.user_id = adm.user_id - Session().add(ug) - Session().commit() - - # fix owners of RepoGroup - for ug in Session().query(RepoGroup).all(): - ug.user_id = adm.user_id - Session().add(ug) - Session().commit() - - def step_13(self): - pass - - def step_14(self): - # fix nullable columns on last_update - for r in RepoModel().get_all(): - if r.updated_on is None: - r.updated_on = datetime.datetime.fromtimestamp(0) - Session().add(r) - Session().commit() - class DbManage(object): - def __init__(self, log_sql, dbconf, root, tests=False, cli_args={}): + def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args={}): self.dbname = dbconf.split('/')[-1] self.tests = tests self.root = root @@ -234,17 +70,21 @@ self.log_sql = log_sql self.db_exists = False self.cli_args = cli_args - self.init_db() + self.init_db(SESSION=SESSION) force_ask = self.cli_args.get('force_ask') if force_ask is not None: global ask_ok ask_ok = lambda *args, **kwargs: force_ask - def init_db(self): - engine = create_engine(self.dburi, echo=self.log_sql) - init_model(engine) - self.sa = Session() + def init_db(self, SESSION=None): + if SESSION: + self.sa = SESSION + else: + #init new sessions + engine = create_engine(self.dburi, echo=self.log_sql) + init_model(engine) + self.sa = Session() def create_tables(self, override=False): """ @@ -257,7 +97,8 @@ else: destroy = ask_ok('Are you sure to destroy old database ? [y/n]') if not destroy: - sys.exit('Nothing tables created') + print 'Nothing done.' + sys.exit(0) if destroy: Base.metadata.drop_all() @@ -289,13 +130,14 @@ '********************** WARNING **********************\n' 'Make sure your version of sqlite is at least 3.7.X. \n' 'Earlier versions are known to fail on some migrations\n' - '*****************************************************\n' - ) + '*****************************************************\n') + upgrade = ask_ok('You are about to perform database upgrade, make ' 'sure You backed up your database before. ' 'Continue ? [y/n]') if not upgrade: - sys.exit('No upgrade performed') + print 'No upgrade performed' + sys.exit(0) repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))), 'rhodecode/lib/dbmigrate') @@ -303,19 +145,19 @@ try: curr_version = api.db_version(db_uri, repository_path) - msg = ('Found current database under version' - ' control with version %s' % curr_version) + msg = ('Found current database under version ' + 'control with version %s' % curr_version) except (RuntimeError, DatabaseNotControlledError): curr_version = 1 - msg = ('Current database is not under version control. Setting' - ' as version %s' % curr_version) + msg = ('Current database is not under version control. Setting ' + 'as version %s' % curr_version) api.version_control(db_uri, repository_path, curr_version) notify(msg) - if curr_version == __dbversion__: - sys.exit('This database is already at the newest version') + print 'This database is already at the newest version' + sys.exit(0) # clear cache keys log.info("Clearing cache keys now...") @@ -329,16 +171,11 @@ _step = None for step in upgrade_steps: notify('performing upgrade step %s' % step) - time.sleep(2) + time.sleep(0.5) api.upgrade(db_uri, repository_path, step) notify('schema upgrade for step %s completed' % (step,)) - fixture = 'step_%s' % step - notify('performing fixture step %s' % fixture) - getattr(UpgradeSteps(self), fixture)() - self.sa.commit() - notify('fixture %s completed' % (fixture,)) _step = step notify('upgrade to version %s successful' % _step) @@ -367,7 +204,7 @@ used mostly for anonymous access """ def_user = self.sa.query(User)\ - .filter(User.username == 'default')\ + .filter(User.username == User.DEFAULT_USER)\ .one() def_user.name = 'Anonymous' @@ -447,7 +284,7 @@ self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL, False) - def create_ui_settings(self): + def create_ui_settings(self, repo_store_path): """ Creates ui settings, fills out hooks and disables dotencode @@ -505,6 +342,15 @@ largefiles.ui_value = '' self.sa.add(largefiles) + # set default largefiles cache dir, defaults to + # /repo location/.cache/largefiles + largefiles = RhodeCodeUi() + largefiles.ui_section = 'largefiles' + largefiles.ui_key = 'usercache' + largefiles.ui_value = os.path.join(repo_store_path, '.cache', + 'largefiles') + self.sa.add(largefiles) + # enable hgsubversion disabled by default hgsubversion = RhodeCodeUi() hgsubversion.ui_section = 'extensions' @@ -521,37 +367,35 @@ hggit.ui_active = False self.sa.add(hggit) - def create_ldap_options(self, skip_existing=False): - """Creates ldap settings""" + def create_auth_plugin_options(self, skip_existing=False): + """ + Create default auth plugin settings, and make it active - for k, v in [('ldap_active', 'false'), ('ldap_host', ''), - ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'), - ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''), - ('ldap_dn_pass', ''), ('ldap_base_dn', ''), - ('ldap_filter', ''), ('ldap_search_scope', ''), - ('ldap_attr_login', ''), ('ldap_attr_firstname', ''), - ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]: + :param skip_existing: + """ - if skip_existing and RhodeCodeSetting.get_by_name(k) is not None: + for k, v, t in [('auth_plugins', 'rhodecode.lib.auth_modules.auth_rhodecode', 'list'), + ('auth_rhodecode_enabled', 'True', 'bool')]: + if skip_existing and RhodeCodeSetting.get_by_name(k) != None: log.debug('Skipping option %s' % k) continue - setting = RhodeCodeSetting(k, v) + setting = RhodeCodeSetting(k, v, t) self.sa.add(setting) def create_default_options(self, skip_existing=False): """Creates default settings""" - for k, v in [ - ('default_repo_enable_locking', False), - ('default_repo_enable_downloads', False), - ('default_repo_enable_statistics', False), - ('default_repo_private', False), - ('default_repo_type', 'hg')]: + for k, v, t in [ + ('default_repo_enable_locking', False, 'bool'), + ('default_repo_enable_downloads', False, 'bool'), + ('default_repo_enable_statistics', False, 'bool'), + ('default_repo_private', False, 'bool'), + ('default_repo_type', 'hg', 'unicode')]: if skip_existing and RhodeCodeSetting.get_by_name(k) is not None: log.debug('Skipping option %s' % k) continue - setting = RhodeCodeSetting(k, v) + setting = RhodeCodeSetting(k, v, t) self.sa.add(setting) def fixup_groups(self): @@ -567,7 +411,7 @@ if default is None: log.debug('missing default permission for group %s adding' % g) - perm_obj = ReposGroupModel()._create_default_perms(g) + perm_obj = RepoGroupModel()._create_default_perms(g) self.sa.add(perm_obj) def reset_permissions(self, username): @@ -651,7 +495,7 @@ def create_settings(self, path): - self.create_ui_settings() + self.create_ui_settings(path) ui_config = [ ('web', 'push_ssl', 'false'), @@ -669,20 +513,25 @@ self.sa.add(ui_conf) settings = [ - ('realm', 'RhodeCode authentication', unicode), - ('title', 'RhodeCode', unicode), - ('ga_code', '', unicode), - ('show_public_icon', True, bool), - ('show_private_icon', True, bool), - ('stylify_metatags', False, bool), - ('dashboard_items', 100, int), - ('show_version', True, bool) + ('realm', 'RhodeCode', 'unicode'), + ('title', '', 'unicode'), + ('ga_code', '', 'unicode'), + ('show_public_icon', True, 'bool'), + ('show_private_icon', True, 'bool'), + ('stylify_metatags', False, 'bool'), + ('dashboard_items', 100, 'int'), + ('admin_grid_items', 25, 'int'), + ('show_version', True, 'bool'), + ('use_gravatar', True, 'bool'), + ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'), + ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'), + ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'), ] for key, val, type_ in settings: - sett = RhodeCodeSetting(key, val) + sett = RhodeCodeSetting(key, val, type_) self.sa.add(sett) - self.create_ldap_options() + self.create_auth_plugin_options() self.create_default_options() log.info('created ui config') @@ -691,7 +540,8 @@ log.info('creating user %s' % username) UserModel().create_or_update(username, password, email, firstname='RhodeCode', lastname='Admin', - active=True, admin=admin) + active=True, admin=admin, + extern_type="rhodecode") def create_default_user(self): log.info('creating default user') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/__init__.py --- a/rhodecode/lib/dbmigrate/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.dbmigrate.__init__ - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Database migration modules - - :created_on: Dec 11, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.dbmigrate.__init__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Database migration modules + +:created_on: Dec 11, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging from sqlalchemy import engine_from_config @@ -48,14 +48,12 @@ def command(self): from pylons import config - add_cache(config) db_uri = config['sqlalchemy.db1.url'] - dbmanage = DbManage(log_sql=True, dbconf=db_uri, - root=config['here'], tests=False) - + root=config['here'], tests=False, + cli_args=self.options.__dict__) dbmanage.upgrade() def update_parser(self): @@ -64,3 +62,14 @@ dest='just_sql', help="Prints upgrade sql for further investigation", default=False) + + self.parser.add_option('--force-yes', + action='store_true', + dest='force_ask', + default=None, + help='Force yes to every question') + self.parser.add_option('--force-no', + action='store_false', + dest='force_ask', + default=None, + help='Force no to every question') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/__init__.py --- a/rhodecode/lib/dbmigrate/schema/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,14 +1,24 @@ # -*- 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 . """ - rhodecode.lib.dbmigrate.schema - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +rhodecode.lib.dbmigrate.schema +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Schemas for migrations - +Schemas for migrations - :created_on: Nov 1, 2011 - :author: marcink - :copyright: (C) 2009-2010 Marcin Kuzminski - :license: , see LICENSE_FILE for more details. +:created_on: Nov 1, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. """ diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_2_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db_1_2_0 - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode <=1.2.X - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db_1_2_0 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode <=1.2.X + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import logging @@ -903,7 +903,7 @@ raise Exception('perm needs to be an instance of Permission class') try: - cls.query().filter(cls.user_id == user_id)\ + cls.query().filter(cls.user_id == user_id) \ .filter(cls.permission == perm).delete() Session.commit() except: @@ -966,7 +966,7 @@ raise Exception('perm needs to be an instance of Permission class') try: - cls.query().filter(cls.users_group_id == users_group_id)\ + cls.query().filter(cls.users_group_id == users_group_id) \ .filter(cls.permission == perm).delete() Session.commit() except: diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_3_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db_1_3_0 - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode <=1.3.X - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db_1_3_0 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode <=1.3.X + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + import os import logging diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_4_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_4_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/db_1_4_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db_1_4_0 - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode <=1.4.X - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db_1_4_0 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode <=1.4.X + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os import logging diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_5_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_5_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/db_1_5_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db_1_5_0 - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode <=1.5.2 - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db_1_5_0 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode <=1.5.2 + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import logging @@ -1221,6 +1221,14 @@ 'hg.create.repository':1 } + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'hg.create.repository', + 'hg.fork.repository', + 'hg.register.manual_activate', + ] + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_5_2.py --- a/rhodecode/lib/dbmigrate/schema/db_1_5_2.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/db_1_5_2.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db_1_5_2 - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode <=1.5.X - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db_1_5_2 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode <=1.5.X + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import logging @@ -701,7 +701,7 @@ def __unicode__(self): return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, - self.repo_name) + safe_unicode(self.repo_name)) @hybrid_property def locked(self): diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_6_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_6_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/db_1_6_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db_1_6_0 - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode <=1.5.X - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db_1_6_0 +~~~~~~~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode <=1.5.X + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import logging @@ -770,7 +770,7 @@ def __unicode__(self): return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, - self.repo_name) + safe_unicode(self.repo_name)) @hybrid_property def locked(self): diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_7_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_7_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/schema/db_1_7_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db - ~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import time @@ -799,7 +799,7 @@ def __unicode__(self): return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, - self.repo_name) + safe_unicode(self.repo_name)) @hybrid_property def locked(self): diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_1_8_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_1_8_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,2270 @@ +# -*- 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 . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import time +import logging +import datetime +import traceback +import hashlib +import collections + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError +from beaker.cache import cache_region, region_invalidate +from webob.exc import HTTPNotFound + +from pylons.i18n.translation import lazy_ugettext as _ + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.lib.vcs.backends.base import EmptyChangeset + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session + +URL_SEP = '/' +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +class BaseModel(object): + """ + Base Model for all classess + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """ + return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + + # also use __json__() if present to get additional fields + _json_attr = getattr(self, '__json__', None) + if _json_attr: + # update with attributes from __json__ + if callable(_json_attr): + _json_attr = _json_attr() + for k, val in _json_attr.iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session().query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def get_or_404(cls, id_): + try: + id_ = int(id_) + except (TypeError, ValueError): + raise HTTPNotFound + + res = cls.query().get(id_) + if not res: + raise HTTPNotFound + return res + + @classmethod + def getAll(cls): + # deprecated and left for backward compatibility + return cls.get_all() + + @classmethod + def get_all(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session().delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + return safe_str(self.__unicode__()) + return '' % (self.__class__.__name__) + + +class RhodeCodeSetting(Base, BaseModel): + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + if self.app_settings_name in ["ldap_active", + "default_repo_enable_statistics", + "default_repo_enable_locking", + "default_repo_private", + "default_repo_enable_downloads"]: + v = str2bool(v) + return v + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value + ) + + @classmethod + def get_by_name(cls, key): + return cls.query()\ + .filter(cls.app_settings_name == key).scalar() + + @classmethod + def get_by_name_or_create(cls, key): + res = cls.get_by_name(key) + if not res: + res = cls(key) + return res + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + if cache: + ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = \ + each.app_settings_value + + return settings + + @classmethod + def get_ldap_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('ldap_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name: row.app_settings_value}) + + return fd + + @classmethod + def get_default_repo_settings(cls, cache=False, strip_prefix=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('default_')).all() + fd = {} + for row in ret: + key = row.app_settings_name + if strip_prefix: + key = remove_prefix(key, prefix='default_') + fd.update({key: row.app_settings_value}) + + return fd + + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + mods += [('git', str(check_git_version()))] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': platform.platform(), + 'rhodecode_version': rhodecode.__version__ + } + return info + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'changegroup.push_logger' + HOOK_PRE_PUSH = 'prechangegroup.pre_push' + HOOK_PULL = 'outgoing.pull_logger' + HOOK_PRE_PULL = 'preoutgoing.pre_pull' + + ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + # def __init__(self, section='', key='', value=''): + # self.ui_section = section + # self.ui_key = key + # self.ui_value = value + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key).scalar() + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def get_repos_location(cls): + return cls.get_by_key('/').ui_value + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key) or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session().add(new_ui) + + def __repr__(self): + return '' % (self.__class__.__name__, self.ui_key, + self.ui_value) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + DEFAULT_USER = 'default' + + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) + name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) + ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + + user_log = relationship('UserLog') + user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') + + repositories = relationship('Repository') + user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') + + repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UserGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + #extra emails for this user + user_emails = relationship('UserEmailMap', cascade='all') + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def firstname(self): + # alias for future + return self.name + + @property + def emails(self): + other = UserEmailMap.query().filter(UserEmailMap.user==self).all() + return [self.email] + [x.email for x in other] + + @property + def ip_addresses(self): + ret = UserIpMap.query().filter(UserIpMap.user == self).all() + return [x.ip_addr for x in ret] + + @property + def username_and_name(self): + return '%s (%s %s)' % (self.username, self.firstname, self.lastname) + + @property + def full_name(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.firstname, self.lastname) + if (self.firstname and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def is_admin(self): + return self.admin + + @property + def AuthUser(self): + """ + Returns instance of AuthUser for this user + """ + from rhodecode.lib.auth import AuthUser + return AuthUser(user_id=self.user_id, api_key=self.api_key, + username=self.username) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + return q.scalar() + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret + + @classmethod + def get_from_cs_author(cls, author): + """ + Tries to get User objects out of commit author string + + :param author: + """ + from rhodecode.lib.helpers import email, author_name + # Valid email in the attribute passed, see if they're in the system + _email = email(author) + if _email: + user = cls.get_by_email(_email, case_insensitive=True) + if user: + return user + # Maybe we can match by username? + _author = author_name(author) + user = cls.get_by_username(_author, case_insensitive=True) + if user: + return user + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session().add(self) + log.debug('updated user %s lastlogin' % self.username) + + @classmethod + def get_first_admin(cls): + user = User.query().filter(User.admin == True).first() + if user is None: + raise Exception('Missing administrative account!') + return user + + @classmethod + def get_default_user(cls, cache=False): + user = User.get_by_username(User.DEFAULT_USER, cache=cache) + if user is None: + raise Exception('Missing default account!') + return user + + def get_api_data(self): + """ + Common function for generating user related data for API + """ + user = self + data = dict( + user_id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + emails=user.emails, + api_key=user.api_key, + active=user.active, + admin=user.admin, + ldap_dn=user.ldap_dn, + last_login=user.last_login, + ip_addresses=user.ip_addresses + ) + return data + + def __json__(self): + data = dict( + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + data.update(self.get_api_data()) + return data + + +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session().query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + +class UserIpMap(Base, BaseModel): + __tablename__ = 'user_ip_map' + __table_args__ = ( + UniqueConstraint('user_id', 'ip_addr'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + user = relationship('User', lazy='joined') + + @classmethod + def _get_ip_range(cls, ip_addr): + from rhodecode.lib import ipaddr + net = ipaddr.IPNetwork(address=ip_addr) + return [str(net.network), str(net.broadcast)] + + def __json__(self): + return dict( + ip_addr=self.ip_addr, + ip_range=self._get_ip_range(self.ip_addr) + ) + + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) + repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.repository_name, + self.action) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UserGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + + members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + users_group_to_perm = relationship('UserGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') + user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') + + user = relationship('User') + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.users_group_id, + self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, user_group_id, cache=False): + user_group = cls.query() + if cache: + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self + + data = dict( + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + active=user_group.users_group_active, + owner=user_group.user.username, + ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members + + return data + + +class UserGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + + user = relationship('User', lazy='joined') + users_group = relationship('UserGroup') + + def __init__(self, gr_id='', u_id=''): + self.users_group_id = gr_id + self.user_id = u_id + + +class RepositoryField(Base, BaseModel): + __tablename__ = 'repositories_fields' + __table_args__ = ( + UniqueConstraint('repository_id', 'field_key'), # no-multi field + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields + + repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) + field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_type = Column("field_type", String(256), nullable=False, unique=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repository = relationship('Repository') + + @property + def field_key_prefixed(self): + return 'ex_%s' % self.field_key + + @classmethod + def un_prefix_key(cls, key): + if key.startswith(cls.PREFIX): + return key[len(cls.PREFIX):] + return key + + @classmethod + def get_by_key_name(cls, key, repo): + row = cls.query()\ + .filter(cls.repository == repo)\ + .filter(cls.field_key == key).scalar() + return row + + +class Repository(Base, BaseModel): + __tablename__ = 'repositories' + __table_args__ = ( + UniqueConstraint('repo_name'), + Index('r_repo_name_idx', 'repo_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + private = Column("private", Boolean(), nullable=True, unique=None, default=None) + enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) + enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) + description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data + + fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + stats = relationship('Statistics', cascade='all', uselist=False) + + followers = relationship('UserFollowing', + primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', + cascade='all') + extra_fields = relationship('RepositoryField', + cascade="all, delete, delete-orphan") + + logs = relationship('UserLog') + comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") + + pull_requests_org = relationship('PullRequest', + primaryjoin='PullRequest.org_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + pull_requests_other = relationship('PullRequest', + primaryjoin='PullRequest.other_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, + safe_unicode(self.repo_name)) + + @hybrid_property + def locked(self): + # always should return [user_id, timelocked] + if self._locked: + _lock_info = self._locked.split(':') + return int(_lock_info[0]), _lock_info[1] + return [None, None] + + @locked.setter + def locked(self, val): + if val and isinstance(val, (list, tuple)): + self._locked = ':'.join(map(str, val)) + else: + self._locked = None + + @hybrid_property + def changeset_cache(self): + from rhodecode.lib.vcs.backends.base import EmptyChangeset + dummy = EmptyChangeset().__json__() + if not self._changeset_cache: + return dummy + try: + return json.loads(self._changeset_cache) + except TypeError: + return dummy + + @changeset_cache.setter + def changeset_cache(self, val): + try: + self._changeset_cache = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def normalize_repo_name(cls, repo_name): + """ + Normalizes os specific repo_name to the format internally stored inside + dabatabase using URL_SEP + + :param cls: + :param repo_name: + """ + return cls.url_sep().join(repo_name.split(os.sep)) + + @classmethod + def get_by_repo_name(cls, repo_name): + q = Session().query(cls).filter(cls.repo_name == repo_name) + q = q.options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group)) + return q.scalar() + + @classmethod + def get_by_full_path(cls, repo_full_path): + repo_name = repo_full_path.split(cls.base_path(), 1)[-1] + repo_name = cls.normalize_repo_name(repo_name) + return cls.get_by_repo_name(repo_name.strip(URL_SEP)) + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + def groups_with_parents(self): + groups = [] + if self.group is None: + return groups + + cur_gr = self.group + groups.insert(0, cur_gr) + while 1: + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + groups.insert(0, gr) + + return groups + + @property + def groups_and_repo(self): + return self.groups_with_parents, self.just_name, self.repo_name + + @LazyProperty + def repo_path(self): + """ + Returns base full path for that repository means where it actually + exists on a filesystem + """ + q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def repo_full_path(self): + p = [self.repo_path] + # we need to split the name by / since this is how we store the + # names in the database, but that eventually needs to be converted + # into a valid system path + p += self.repo_name.split(Repository.url_sep()) + return os.path.join(*map(safe_unicode, p)) + + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + def _ui(self): + """ + Creates an db based ui object for this repository + """ + from rhodecode.lib.utils import make_ui + return make_ui('db', clear_session=False) + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + def get_api_data(self): + """ + Common function for generating repo api data + + """ + repo = self + data = dict( + repo_id=repo.repo_id, + repo_name=repo.repo_name, + repo_type=repo.repo_type, + clone_uri=repo.clone_uri, + private=repo.private, + created_on=repo.created_on, + description=repo.description, + landing_rev=repo.landing_rev, + owner=repo.user.username, + fork_of=repo.fork.repo_name if repo.fork else None, + enable_statistics=repo.enable_statistics, + enable_locking=repo.enable_locking, + enable_downloads=repo.enable_downloads, + last_changeset=repo.changeset_cache, + locked_by=User.get(self.locked[0]).get_api_data() \ + if self.locked[0] else None, + locked_date=time_to_datetime(self.locked[1]) \ + if self.locked[1] else None + ) + rc_config = RhodeCodeSetting.get_app_settings() + repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) + if repository_fields: + for f in self.extra_fields: + data[f.field_key_prefixed] = f.field_value + + return data + + @classmethod + def lock(cls, repo, user_id, lock_time=None): + if not lock_time: + lock_time = time.time() + repo.locked = [user_id, lock_time] + Session().add(repo) + Session().commit() + + @classmethod + def unlock(cls, repo): + repo.locked = None + Session().add(repo) + Session().commit() + + @classmethod + def getlock(cls, repo): + return repo.locked + + @property + def last_db_change(self): + return self.updated_on + + def clone_url(self, **override): + from pylons import url + from urlparse import urlparse + import urllib + parsed_url = urlparse(url('home', qualified=True)) + default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' + decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) + args = { + 'user': '', + 'pass': '', + 'scheme': parsed_url.scheme, + 'netloc': parsed_url.netloc, + 'prefix': decoded_path, + 'path': self.repo_name + } + + args.update(override) + return default_clone_uri % args + + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev=None): + return get_changeset_safe(self.scm_instance, rev) + + def get_landing_changeset(self): + """ + Returns landing changeset, or if that doesn't exist returns the tip + """ + cs = self.get_changeset(self.landing_rev) or self.get_changeset() + return cs + + def update_changeset_cache(self, cs_cache=None): + """ + Update cache of last changeset for repository, keys should be:: + + short_id + raw_id + revision + message + date + author + + :param cs_cache: + """ + from rhodecode.lib.vcs.backends.base import BaseChangeset + if cs_cache is None: + cs_cache = EmptyChangeset() + # use no-cache version here + scm_repo = self.scm_instance_no_cache() + if scm_repo: + cs_cache = scm_repo.get_changeset() + + if isinstance(cs_cache, BaseChangeset): + cs_cache = cs_cache.__json__() + + if (cs_cache != self.changeset_cache or not self.changeset_cache): + _default = datetime.datetime.fromtimestamp(0) + last_change = cs_cache.get('date') or _default + log.debug('updated repo %s with new cs cache %s' + % (self.repo_name, cs_cache)) + self.updated_on = last_change + self.changeset_cache = cs_cache + Session().add(self) + Session().commit() + else: + log.debug('Skipping repo:%s already with latest changes' + % self.repo_name) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author + + @property + def last_change(self): + return self.scm_instance.last_change + + def get_comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = collections.defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + + #maybe we have open new pullrequest without a status ? + stat = ChangesetStatus.STATUS_UNDER_REVIEW + status_lbl = ChangesetStatus.get_status_lbl(stat) + for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): + for rev in pr.revisions: + pr_id = pr.pull_request_id + pr_repo = pr.other_repo.repo_name + grouped[rev] = [stat, status_lbl, pr_id, pr_repo] + + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + + def _repo_size(self): + from rhodecode.lib import helpers as h + log.debug('calculating repository size...') + return h.format_byte_size(self.scm_instance.size) + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + def set_invalidate(self): + """ + Mark caches of this repo as invalid. + """ + CacheInvalidation.set_invalidate(self.repo_name) + + def scm_instance_no_cache(self): + return self.__get_instance() + + @property + def scm_instance(self): + import rhodecode + full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) + if full_cache: + return self.scm_instance_cached() + return self.__get_instance() + + def scm_instance_cached(self, valid_cache_keys=None): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + + valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) + if not valid: + log.debug('Cache for %s invalidated, getting new object' % (rn)) + region_invalidate(_c, None, rn) + else: + log.debug('Getting obj for %s from cache' % (rn)) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository from %s' + % (alias, repo_full_path)) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + log.error('Perhaps this repository is in db and not in ' + 'filesystem run rescan repositories with ' + '"destroy old data " option from admin panel') + return + + if alias == 'hg': + + repo = backend(safe_str(repo_full_path), create=False, + baseui=self._ui) + # skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + __mapper_args__ = {'order_by': 'group_name'} + + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) + user = relationship('User') + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def groups_choices(cls, groups=None, show_empty_group=True): + from webhelpers.html import literal as _literal + if not groups: + groups = cls.query().all() + + repo_groups = [] + if show_empty_group: + repo_groups = [('-1', u'-- %s --' % _('top level'))] + sep = ' » ' + _name = lambda k: _literal(sep.join(k)) + + repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) + for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def _recursive_objects(self, include_repos=True): + all_ = [] + + def _get_members(root_gr): + if include_repos: + for r in root_gr.repositories: + all_.append(r) + childs = root_gr.children.all() + if childs: + for gr in childs: + all_.append(gr) + _get_members(gr) + + _get_members(self) + return [self] + all_ + + def recursive_groups_and_repos(self): + """ + Recursive return all groups, with repositories in those groups + """ + return self._recursive_objects() + + def recursive_groups(self): + """ + Returns all children groups for this group including children of children + """ + return self._recursive_objects(include_repos=False) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PERMS = [ + ('hg.admin', _('RhodeCode Administrator')), + + ('repository.none', _('Repository no access')), + ('repository.read', _('Repository read access')), + ('repository.write', _('Repository write access')), + ('repository.admin', _('Repository admin access')), + + ('group.none', _('Repository group no access')), + ('group.read', _('Repository group read access')), + ('group.write', _('Repository group write access')), + ('group.admin', _('Repository group admin access')), + + ('usergroup.none', _('User group no access')), + ('usergroup.read', _('User group read access')), + ('usergroup.write', _('User group write access')), + ('usergroup.admin', _('User group admin access')), + + ('hg.repogroup.create.false', _('Repository Group creation disabled')), + ('hg.repogroup.create.true', _('Repository Group creation enabled')), + + ('hg.usergroup.create.false', _('User Group creation disabled')), + ('hg.usergroup.create.true', _('User Group creation enabled')), + + ('hg.create.none', _('Repository creation disabled')), + ('hg.create.repository', _('Repository creation enabled')), + + ('hg.fork.none', _('Repository forking disabled')), + ('hg.fork.repository', _('Repository forking enabled')), + + ('hg.register.none', _('Registration disabled')), + ('hg.register.manual_activate', _('User Registration with manual account activation')), + ('hg.register.auto_activate', _('User Registration with automatic account activation')), + + ('hg.extern_activate.manual', _('Manual activation of external account')), + ('hg.extern_activate.auto', _('Automatic activation of external account')), + + ] + + #definition of system default permissions for DEFAULT user + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'usergroup.read', + 'hg.create.repository', + 'hg.fork.repository', + 'hg.register.manual_activate', + 'hg.extern_activate.auto', + ] + + # defines which permissions are more important higher the more important + # Weight defines which permissions are more important. + # The higher number the more important. + PERM_WEIGHTS = { + 'repository.none': 0, + 'repository.read': 1, + 'repository.write': 3, + 'repository.admin': 4, + + 'group.none': 0, + 'group.read': 1, + 'group.write': 3, + 'group.admin': 4, + + 'usergroup.none': 0, + 'usergroup.read': 1, + 'usergroup.write': 3, + 'usergroup.admin': 4, + 'hg.repogroup.create.false': 0, + 'hg.repogroup.create.true': 1, + + 'hg.usergroup.create.false': 0, + 'hg.usergroup.create.true': 1, + + 'hg.fork.none': 0, + 'hg.fork.repository': 1, + 'hg.create.none': 0, + 'hg.create.repository': 1 + } + + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session().query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_user_group_perms(cls, default_user_id): + q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ + .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ + .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserUserGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.repository) + + +class UserUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'user_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + user_group = relationship('UserGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, user_group, permission): + n = cls() + n.user = user + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.user_group) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.permission) + + +class UserGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) + + +class UserGroupUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_group_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), + CheckConstraint('target_user_group_id != user_group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') + user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') + permission = relationship('Permission') + + @classmethod + def create(cls, target_user_group, user_group, permission): + n = cls() + n.target_user_group = target_user_group + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.target_user_group, self.user_group) + + +class UserGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + +class UserGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + Index('key_idx', 'cache_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + # cache_id, not used + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + # cache_key as created by _get_cache_key + cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # cache_args is a repo_name + cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # instance sets cache_active True when it is caching, + # other instances set cache_active to False to indicate that this cache is invalid + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, repo_name=''): + self.cache_key = cache_key + self.cache_args = repo_name + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, + self.cache_id, self.cache_key, self.cache_active) + + def _cache_key_partition(self): + prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) + return prefix, repo_name, suffix + + def get_prefix(self): + """ + get prefix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[0] + + def get_suffix(self): + """ + get suffix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[2] + + @classmethod + def clear_cache(cls): + """ + Delete all cache keys from database. + Should only be run when all instances are down and all entries thus stale. + """ + cls.query().delete() + Session().commit() + + @classmethod + def _get_cache_key(cls, key): + """ + Wrapper for generating a unique cache key for this instance and "key". + key must / will start with a repo_name which will be stored in .cache_args . + """ + import rhodecode + prefix = rhodecode.CONFIG.get('instance_id', '') + return "%s%s" % (prefix, key) + + @classmethod + def set_invalidate(cls, repo_name): + """ + Mark all caches of a repo as invalid in the database. + """ + inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() + + try: + for inv_obj in inv_objs: + log.debug('marking %s key for invalidation based on repo_name=%s' + % (inv_obj, safe_str(repo_name))) + inv_obj.cache_active = False + Session().add(inv_obj) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + + @classmethod + def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): + """ + Mark this cache key as active and currently cached. + Return True if the existing cache registration still was valid. + Return False to indicate that it had been invalidated and caches should be refreshed. + """ + + key = (repo_name + '_' + kind) if kind else repo_name + cache_key = cls._get_cache_key(key) + + if valid_cache_keys and cache_key in valid_cache_keys: + return True + + try: + inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() + if not inv_obj: + inv_obj = CacheInvalidation(cache_key, repo_name) + was_valid = inv_obj.cache_active + inv_obj.cache_active = True + Session().add(inv_obj) + Session().commit() + return was_valid + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + return False + + @classmethod + def get_valid_cache_keys(cls): + """ + Return opaque object with information of which caches still are valid + and can be used without checking for invalidation. + """ + return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + Index('cc_revision_idx', 'revision'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + line_no = Column('line_no', Unicode(10), nullable=True) + hl_lines = Column('hl_lines', Unicode(512), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', UnicodeText(25000), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_users(cls, revision=None, pull_request_id=None): + """ + Returns user associated with this ChangesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + q = Session().query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' + + STATUSES = [ + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), + ] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + # values for .status + STATUS_NEW = u'new' + STATUS_OPEN = u'open' + STATUS_CLOSED = u'closed' + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', UnicodeText(10240), nullable=True) + status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + @property + def org_ref_parts(self): + return self.org_ref.split(':') + + @property + def other_ref_parts(self): + return self.other_ref.split(':') + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers', + cascade="all, delete, delete-orphan") + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + statuses = relationship('ChangesetStatus') + comments = relationship('ChangesetComment', + cascade="all, delete, delete-orphan") + + def is_closed(self): + return self.status == self.STATUS_CLOSED + + @property + def last_review_status(self): + return self.statuses[-1].status if self.statuses else '' + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', UnicodeText(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self)\ + .order_by(UserNotification.user_id.asc()).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session().add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session().add(self) + + +class Gist(Base, BaseModel): + __tablename__ = 'gists' + __table_args__ = ( + Index('g_gist_access_id_idx', 'gist_access_id'), + Index('g_created_on_idx', 'created_on'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + GIST_PUBLIC = u'public' + GIST_PRIVATE = u'private' + + gist_id = Column('gist_id', Integer(), primary_key=True) + gist_access_id = Column('gist_access_id', Unicode(250)) + gist_description = Column('gist_description', UnicodeText(1024)) + gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) + gist_expires = Column('gist_expires', Float(53), nullable=False) + gist_type = Column('gist_type', Unicode(128), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + owner = relationship('User') + + @classmethod + def get_or_404(cls, id_): + res = cls.query().filter(cls.gist_access_id == id_).scalar() + if not res: + raise HTTPNotFound + return res + + @classmethod + def get_by_access_id(cls, gist_access_id): + return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() + + def gist_url(self): + import rhodecode + alias_url = rhodecode.CONFIG.get('gist_alias_url') + if alias_url: + return alias_url.replace('{gistid}', self.gist_access_id) + + from pylons import url + return url('gist', gist_id=self.gist_access_id, qualified=True) + + @classmethod + def base_path(cls): + """ + Returns base path when all gists are stored + + :param cls: + """ + from rhodecode.model.gist import GIST_STORE_LOC + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == URL_SEP) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return os.path.join(q.one().ui_value, GIST_STORE_LOC) + + def get_api_data(self): + """ + Common function for generating gist related data for API + """ + gist = self + data = dict( + gist_id=gist.gist_id, + type=gist.gist_type, + access_id=gist.gist_access_id, + description=gist.gist_description, + url=gist.gist_url(), + expires=gist.gist_expires, + created_on=gist.created_on, + ) + return data + + def __json__(self): + data = dict( + ) + data.update(self.get_api_data()) + return data + ## SCM functions + + @property + def scm_instance(self): + from rhodecode.lib.vcs import get_repo + base_path = self.base_path() + return get_repo(os.path.join(*map(safe_str, + [base_path, self.gist_access_id]))) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_2_0_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_2_0_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,2330 @@ +# -*- 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 . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import time +import logging +import datetime +import traceback +import hashlib +import collections +import functools + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError +from beaker.cache import cache_region, region_invalidate +from webob.exc import HTTPNotFound + +from pylons.i18n.translation import lazy_ugettext as _ + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.lib.vcs.backends.base import EmptyChangeset + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session + +URL_SEP = '/' +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +class BaseModel(object): + """ + Base Model for all classess + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """ + return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + + # also use __json__() if present to get additional fields + _json_attr = getattr(self, '__json__', None) + if _json_attr: + # update with attributes from __json__ + if callable(_json_attr): + _json_attr = _json_attr() + for k, val in _json_attr.iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session().query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def get_or_404(cls, id_): + try: + id_ = int(id_) + except (TypeError, ValueError): + raise HTTPNotFound + + res = cls.query().get(id_) + if not res: + raise HTTPNotFound + return res + + @classmethod + def getAll(cls): + # deprecated and left for backward compatibility + return cls.get_all() + + @classmethod + def get_all(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session().delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + return safe_str(self.__unicode__()) + return '' % (self.__class__.__name__) + + +class RhodeCodeSetting(Base, BaseModel): + SETTINGS_TYPES = { + 'str': safe_str, + 'int': safe_int, + 'unicode': safe_unicode, + 'bool': str2bool, + 'list': functools.partial(aslist, sep=',') + } + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + _type = self.app_settings_type + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + return converter(v) + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + @hybrid_property + def app_settings_type(self): + return self._app_settings_type + + @app_settings_type.setter + def app_settings_type(self, val): + if val not in self.SETTINGS_TYPES: + raise Exception('type must be one of %s got %s' + % (self.SETTINGS_TYPES.keys(), val)) + self._app_settings_type = val + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value, self.app_settings_type + ) + + @classmethod + def get_by_name(cls, key): + return cls.query()\ + .filter(cls.app_settings_name == key).scalar() + + @classmethod + def get_by_name_or_create(cls, key, val='', type='unicode'): + res = cls.get_by_name(key) + if not res: + res = cls(key, val, type) + return res + + @classmethod + def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): + """ + Creates or updates RhodeCode setting. If updates is triggered it will only + update parameters that are explicityl set Optional instance will be skipped + + :param key: + :param val: + :param type: + :return: + """ + res = cls.get_by_name(key) + if not res: + val = Optional.extract(val) + type = Optional.extract(type) + res = cls(key, val, type) + else: + res.app_settings_name = key + if not isinstance(val, Optional): + # update if set + res.app_settings_value = val + if not isinstance(type, Optional): + # update if set + res.app_settings_type = type + return res + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + if cache: + ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = \ + each.app_settings_value + + return settings + + @classmethod + def get_auth_plugins(cls, cache=False): + auth_plugins = cls.get_by_name("auth_plugins").app_settings_value + return auth_plugins + + @classmethod + def get_auth_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('auth_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name: row.app_settings_value}) + + return fd + + @classmethod + def get_default_repo_settings(cls, cache=False, strip_prefix=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('default_')).all() + fd = {} + for row in ret: + key = row.app_settings_name + if strip_prefix: + key = remove_prefix(key, prefix='default_') + fd.update({key: row.app_settings_value}) + + return fd + + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': platform.platform(), + 'rhodecode_version': rhodecode.__version__, + 'git_version': str(check_git_version()), + 'git_path': rhodecode.CONFIG.get('git_path') + } + return info + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'changegroup.push_logger' + HOOK_PRE_PUSH = 'prechangegroup.pre_push' + HOOK_PULL = 'outgoing.pull_logger' + HOOK_PRE_PULL = 'preoutgoing.pre_pull' + + ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + # def __init__(self, section='', key='', value=''): + # self.ui_section = section + # self.ui_key = key + # self.ui_value = value + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key).scalar() + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def get_repos_location(cls): + return cls.get_by_key('/').ui_value + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key) or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session().add(new_ui) + + def __repr__(self): + return '' % (self.__class__.__name__, self.ui_key, + self.ui_value) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + DEFAULT_USER = 'default' + + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) + name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) + extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + #for migration reasons, this is going to be later deleted + ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user_log = relationship('UserLog') + user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') + + repositories = relationship('Repository') + user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') + + repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UserGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + #extra emails for this user + user_emails = relationship('UserEmailMap', cascade='all') + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def firstname(self): + # alias for future + return self.name + + @property + def emails(self): + other = UserEmailMap.query().filter(UserEmailMap.user==self).all() + return [self.email] + [x.email for x in other] + + @property + def ip_addresses(self): + ret = UserIpMap.query().filter(UserIpMap.user == self).all() + return [x.ip_addr for x in ret] + + @property + def username_and_name(self): + return '%s (%s %s)' % (self.username, self.firstname, self.lastname) + + @property + def full_name(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.firstname, self.lastname) + if (self.firstname and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def is_admin(self): + return self.admin + + @property + def AuthUser(self): + """ + Returns instance of AuthUser for this user + """ + from rhodecode.lib.auth import AuthUser + return AuthUser(user_id=self.user_id, api_key=self.api_key, + username=self.username) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + return q.scalar() + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret + + @classmethod + def get_from_cs_author(cls, author): + """ + Tries to get User objects out of commit author string + + :param author: + """ + from rhodecode.lib.helpers import email, author_name + # Valid email in the attribute passed, see if they're in the system + _email = email(author) + if _email: + user = cls.get_by_email(_email, case_insensitive=True) + if user: + return user + # Maybe we can match by username? + _author = author_name(author) + user = cls.get_by_username(_author, case_insensitive=True) + if user: + return user + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session().add(self) + log.debug('updated user %s lastlogin' % self.username) + + @classmethod + def get_first_admin(cls): + user = User.query().filter(User.admin == True).first() + if user is None: + raise Exception('Missing administrative account!') + return user + + @classmethod + def get_default_user(cls, cache=False): + user = User.get_by_username(User.DEFAULT_USER, cache=cache) + if user is None: + raise Exception('Missing default account!') + return user + + def get_api_data(self): + """ + Common function for generating user related data for API + """ + user = self + data = dict( + user_id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + emails=user.emails, + api_key=user.api_key, + active=user.active, + admin=user.admin, + extern_type=user.extern_type, + extern_name=user.extern_name, + last_login=user.last_login, + ip_addresses=user.ip_addresses + ) + return data + + def __json__(self): + data = dict( + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + data.update(self.get_api_data()) + return data + + +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session().query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + +class UserIpMap(Base, BaseModel): + __tablename__ = 'user_ip_map' + __table_args__ = ( + UniqueConstraint('user_id', 'ip_addr'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + user = relationship('User', lazy='joined') + + @classmethod + def _get_ip_range(cls, ip_addr): + from rhodecode.lib import ipaddr + net = ipaddr.IPNetwork(address=ip_addr) + return [str(net.network), str(net.broadcast)] + + def __json__(self): + return dict( + ip_addr=self.ip_addr, + ip_range=self._get_ip_range(self.ip_addr) + ) + + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) + repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.repository_name, + self.action) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UserGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + # don't trigger lazy load for migrations + #members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + users_group_to_perm = relationship('UserGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') + user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') + + user = relationship('User') + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.users_group_id, + self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, user_group_id, cache=False): + user_group = cls.query() + if cache: + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self + + data = dict( + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + group_description=user_group.user_group_description, + active=user_group.users_group_active, + owner=user_group.user.username, + ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members + + return data + + +class UserGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + + user = relationship('User', lazy='joined') + users_group = relationship('UserGroup') + + def __init__(self, gr_id='', u_id=''): + self.users_group_id = gr_id + self.user_id = u_id + + +class RepositoryField(Base, BaseModel): + __tablename__ = 'repositories_fields' + __table_args__ = ( + UniqueConstraint('repository_id', 'field_key'), # no-multi field + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields + + repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) + field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_type = Column("field_type", String(256), nullable=False, unique=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repository = relationship('Repository') + + @property + def field_key_prefixed(self): + return 'ex_%s' % self.field_key + + @classmethod + def un_prefix_key(cls, key): + if key.startswith(cls.PREFIX): + return key[len(cls.PREFIX):] + return key + + @classmethod + def get_by_key_name(cls, key, repo): + row = cls.query()\ + .filter(cls.repository == repo)\ + .filter(cls.field_key == key).scalar() + return row + + +class Repository(Base, BaseModel): + __tablename__ = 'repositories' + __table_args__ = ( + UniqueConstraint('repo_name'), + Index('r_repo_name_idx', 'repo_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + private = Column("private", Boolean(), nullable=True, unique=None, default=None) + enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) + enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) + description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data + + fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + stats = relationship('Statistics', cascade='all', uselist=False) + + followers = relationship('UserFollowing', + primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', + cascade='all') + extra_fields = relationship('RepositoryField', + cascade="all, delete, delete-orphan") + + logs = relationship('UserLog') + comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") + + pull_requests_org = relationship('PullRequest', + primaryjoin='PullRequest.org_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + pull_requests_other = relationship('PullRequest', + primaryjoin='PullRequest.other_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, + safe_unicode(self.repo_name)) + + @hybrid_property + def locked(self): + # always should return [user_id, timelocked] + if self._locked: + _lock_info = self._locked.split(':') + return int(_lock_info[0]), _lock_info[1] + return [None, None] + + @locked.setter + def locked(self, val): + if val and isinstance(val, (list, tuple)): + self._locked = ':'.join(map(str, val)) + else: + self._locked = None + + @hybrid_property + def changeset_cache(self): + from rhodecode.lib.vcs.backends.base import EmptyChangeset + dummy = EmptyChangeset().__json__() + if not self._changeset_cache: + return dummy + try: + return json.loads(self._changeset_cache) + except TypeError: + return dummy + + @changeset_cache.setter + def changeset_cache(self, val): + try: + self._changeset_cache = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def normalize_repo_name(cls, repo_name): + """ + Normalizes os specific repo_name to the format internally stored inside + dabatabase using URL_SEP + + :param cls: + :param repo_name: + """ + return cls.url_sep().join(repo_name.split(os.sep)) + + @classmethod + def get_by_repo_name(cls, repo_name): + q = Session().query(cls).filter(cls.repo_name == repo_name) + q = q.options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group)) + return q.scalar() + + @classmethod + def get_by_full_path(cls, repo_full_path): + repo_name = repo_full_path.split(cls.base_path(), 1)[-1] + repo_name = cls.normalize_repo_name(repo_name) + return cls.get_by_repo_name(repo_name.strip(URL_SEP)) + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + def groups_with_parents(self): + groups = [] + if self.group is None: + return groups + + cur_gr = self.group + groups.insert(0, cur_gr) + while 1: + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + groups.insert(0, gr) + + return groups + + @property + def groups_and_repo(self): + return self.groups_with_parents, self.just_name, self.repo_name + + @LazyProperty + def repo_path(self): + """ + Returns base full path for that repository means where it actually + exists on a filesystem + """ + q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def repo_full_path(self): + p = [self.repo_path] + # we need to split the name by / since this is how we store the + # names in the database, but that eventually needs to be converted + # into a valid system path + p += self.repo_name.split(Repository.url_sep()) + return os.path.join(*map(safe_unicode, p)) + + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + def _ui(self): + """ + Creates an db based ui object for this repository + """ + from rhodecode.lib.utils import make_ui + return make_ui('db', clear_session=False) + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + def get_api_data(self): + """ + Common function for generating repo api data + + """ + repo = self + data = dict( + repo_id=repo.repo_id, + repo_name=repo.repo_name, + repo_type=repo.repo_type, + clone_uri=repo.clone_uri, + private=repo.private, + created_on=repo.created_on, + description=repo.description, + landing_rev=repo.landing_rev, + owner=repo.user.username, + fork_of=repo.fork.repo_name if repo.fork else None, + enable_statistics=repo.enable_statistics, + enable_locking=repo.enable_locking, + enable_downloads=repo.enable_downloads, + last_changeset=repo.changeset_cache, + locked_by=User.get(self.locked[0]).get_api_data() \ + if self.locked[0] else None, + locked_date=time_to_datetime(self.locked[1]) \ + if self.locked[1] else None + ) + rc_config = RhodeCodeSetting.get_app_settings() + repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) + if repository_fields: + for f in self.extra_fields: + data[f.field_key_prefixed] = f.field_value + + return data + + @classmethod + def lock(cls, repo, user_id, lock_time=None): + if not lock_time: + lock_time = time.time() + repo.locked = [user_id, lock_time] + Session().add(repo) + Session().commit() + + @classmethod + def unlock(cls, repo): + repo.locked = None + Session().add(repo) + Session().commit() + + @classmethod + def getlock(cls, repo): + return repo.locked + + @property + def last_db_change(self): + return self.updated_on + + def clone_url(self, **override): + from pylons import url + from urlparse import urlparse + import urllib + parsed_url = urlparse(url('home', qualified=True)) + default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' + decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) + args = { + 'user': '', + 'pass': '', + 'scheme': parsed_url.scheme, + 'netloc': parsed_url.netloc, + 'prefix': decoded_path, + 'path': self.repo_name + } + + args.update(override) + return default_clone_uri % args + + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev=None): + return get_changeset_safe(self.scm_instance, rev) + + def get_landing_changeset(self): + """ + Returns landing changeset, or if that doesn't exist returns the tip + """ + cs = self.get_changeset(self.landing_rev) or self.get_changeset() + return cs + + def update_changeset_cache(self, cs_cache=None): + """ + Update cache of last changeset for repository, keys should be:: + + short_id + raw_id + revision + message + date + author + + :param cs_cache: + """ + from rhodecode.lib.vcs.backends.base import BaseChangeset + if cs_cache is None: + cs_cache = EmptyChangeset() + # use no-cache version here + scm_repo = self.scm_instance_no_cache() + if scm_repo: + cs_cache = scm_repo.get_changeset() + + if isinstance(cs_cache, BaseChangeset): + cs_cache = cs_cache.__json__() + + if (cs_cache != self.changeset_cache or not self.changeset_cache): + _default = datetime.datetime.fromtimestamp(0) + last_change = cs_cache.get('date') or _default + log.debug('updated repo %s with new cs cache %s' + % (self.repo_name, cs_cache)) + self.updated_on = last_change + self.changeset_cache = cs_cache + Session().add(self) + Session().commit() + else: + log.debug('Skipping repo:%s already with latest changes' + % self.repo_name) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author + + @property + def last_change(self): + return self.scm_instance.last_change + + def get_comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = collections.defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + + #maybe we have open new pullrequest without a status ? + stat = ChangesetStatus.STATUS_UNDER_REVIEW + status_lbl = ChangesetStatus.get_status_lbl(stat) + for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): + for rev in pr.revisions: + pr_id = pr.pull_request_id + pr_repo = pr.other_repo.repo_name + grouped[rev] = [stat, status_lbl, pr_id, pr_repo] + + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + + def _repo_size(self): + from rhodecode.lib import helpers as h + log.debug('calculating repository size...') + return h.format_byte_size(self.scm_instance.size) + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + def set_invalidate(self): + """ + Mark caches of this repo as invalid. + """ + CacheInvalidation.set_invalidate(self.repo_name) + + def scm_instance_no_cache(self): + return self.__get_instance() + + @property + def scm_instance(self): + import rhodecode + full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) + if full_cache: + return self.scm_instance_cached() + return self.__get_instance() + + def scm_instance_cached(self, valid_cache_keys=None): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + + valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) + if not valid: + log.debug('Cache for %s invalidated, getting new object' % (rn)) + region_invalidate(_c, None, rn) + else: + log.debug('Getting obj for %s from cache' % (rn)) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository from %s' + % (alias, repo_full_path)) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + log.error('Perhaps this repository is in db and not in ' + 'filesystem run rescan repositories with ' + '"destroy old data " option from admin panel') + return + + if alias == 'hg': + + repo = backend(safe_str(repo_full_path), create=False, + baseui=self._ui) + # skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + __mapper_args__ = {'order_by': 'group_name'} + + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) + user = relationship('User') + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def groups_choices(cls, groups=None, show_empty_group=True): + from webhelpers.html import literal as _literal + if not groups: + groups = cls.query().all() + + repo_groups = [] + if show_empty_group: + repo_groups = [('-1', u'-- %s --' % _('top level'))] + sep = ' » ' + _name = lambda k: _literal(sep.join(k)) + + repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) + for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def _recursive_objects(self, include_repos=True): + all_ = [] + + def _get_members(root_gr): + if include_repos: + for r in root_gr.repositories: + all_.append(r) + childs = root_gr.children.all() + if childs: + for gr in childs: + all_.append(gr) + _get_members(gr) + + _get_members(self) + return [self] + all_ + + def recursive_groups_and_repos(self): + """ + Recursive return all groups, with repositories in those groups + """ + return self._recursive_objects() + + def recursive_groups(self): + """ + Returns all children groups for this group including children of children + """ + return self._recursive_objects(include_repos=False) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PERMS = [ + ('hg.admin', _('RhodeCode Administrator')), + + ('repository.none', _('Repository no access')), + ('repository.read', _('Repository read access')), + ('repository.write', _('Repository write access')), + ('repository.admin', _('Repository admin access')), + + ('group.none', _('Repository group no access')), + ('group.read', _('Repository group read access')), + ('group.write', _('Repository group write access')), + ('group.admin', _('Repository group admin access')), + + ('usergroup.none', _('User group no access')), + ('usergroup.read', _('User group read access')), + ('usergroup.write', _('User group write access')), + ('usergroup.admin', _('User group admin access')), + + ('hg.repogroup.create.false', _('Repository Group creation disabled')), + ('hg.repogroup.create.true', _('Repository Group creation enabled')), + + ('hg.usergroup.create.false', _('User Group creation disabled')), + ('hg.usergroup.create.true', _('User Group creation enabled')), + + ('hg.create.none', _('Repository creation disabled')), + ('hg.create.repository', _('Repository creation enabled')), + + ('hg.fork.none', _('Repository forking disabled')), + ('hg.fork.repository', _('Repository forking enabled')), + + ('hg.register.none', _('Registration disabled')), + ('hg.register.manual_activate', _('User Registration with manual account activation')), + ('hg.register.auto_activate', _('User Registration with automatic account activation')), + + ('hg.extern_activate.manual', _('Manual activation of external account')), + ('hg.extern_activate.auto', _('Automatic activation of external account')), + + ] + + #definition of system default permissions for DEFAULT user + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'usergroup.read', + 'hg.create.repository', + 'hg.fork.repository', + 'hg.register.manual_activate', + 'hg.extern_activate.auto', + ] + + # defines which permissions are more important higher the more important + # Weight defines which permissions are more important. + # The higher number the more important. + PERM_WEIGHTS = { + 'repository.none': 0, + 'repository.read': 1, + 'repository.write': 3, + 'repository.admin': 4, + + 'group.none': 0, + 'group.read': 1, + 'group.write': 3, + 'group.admin': 4, + + 'usergroup.none': 0, + 'usergroup.read': 1, + 'usergroup.write': 3, + 'usergroup.admin': 4, + 'hg.repogroup.create.false': 0, + 'hg.repogroup.create.true': 1, + + 'hg.usergroup.create.false': 0, + 'hg.usergroup.create.true': 1, + + 'hg.fork.none': 0, + 'hg.fork.repository': 1, + 'hg.create.none': 0, + 'hg.create.repository': 1 + } + + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session().query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_user_group_perms(cls, default_user_id): + q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ + .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ + .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserUserGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.repository) + + +class UserUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'user_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + user_group = relationship('UserGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, user_group, permission): + n = cls() + n.user = user + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.user_group) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.permission) + + +class UserGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) + + +class UserGroupUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_group_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), + CheckConstraint('target_user_group_id != user_group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') + user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') + permission = relationship('Permission') + + @classmethod + def create(cls, target_user_group, user_group, permission): + n = cls() + n.target_user_group = target_user_group + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.target_user_group, self.user_group) + + +class UserGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + +class UserGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + Index('key_idx', 'cache_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + # cache_id, not used + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + # cache_key as created by _get_cache_key + cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # cache_args is a repo_name + cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # instance sets cache_active True when it is caching, + # other instances set cache_active to False to indicate that this cache is invalid + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, repo_name=''): + self.cache_key = cache_key + self.cache_args = repo_name + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, + self.cache_id, self.cache_key, self.cache_active) + + def _cache_key_partition(self): + prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) + return prefix, repo_name, suffix + + def get_prefix(self): + """ + get prefix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[0] + + def get_suffix(self): + """ + get suffix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[2] + + @classmethod + def clear_cache(cls): + """ + Delete all cache keys from database. + Should only be run when all instances are down and all entries thus stale. + """ + cls.query().delete() + Session().commit() + + @classmethod + def _get_cache_key(cls, key): + """ + Wrapper for generating a unique cache key for this instance and "key". + key must / will start with a repo_name which will be stored in .cache_args . + """ + import rhodecode + prefix = rhodecode.CONFIG.get('instance_id', '') + return "%s%s" % (prefix, key) + + @classmethod + def set_invalidate(cls, repo_name, delete=False): + """ + Mark all caches of a repo as invalid in the database. + """ + inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() + + try: + for inv_obj in inv_objs: + log.debug('marking %s key for invalidation based on repo_name=%s' + % (inv_obj, safe_str(repo_name))) + if delete: + Session().delete(inv_obj) + else: + inv_obj.cache_active = False + Session().add(inv_obj) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + + @classmethod + def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): + """ + Mark this cache key as active and currently cached. + Return True if the existing cache registration still was valid. + Return False to indicate that it had been invalidated and caches should be refreshed. + """ + + key = (repo_name + '_' + kind) if kind else repo_name + cache_key = cls._get_cache_key(key) + + if valid_cache_keys and cache_key in valid_cache_keys: + return True + + try: + inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() + if not inv_obj: + inv_obj = CacheInvalidation(cache_key, repo_name) + was_valid = inv_obj.cache_active + inv_obj.cache_active = True + Session().add(inv_obj) + Session().commit() + return was_valid + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + return False + + @classmethod + def get_valid_cache_keys(cls): + """ + Return opaque object with information of which caches still are valid + and can be used without checking for invalidation. + """ + return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + Index('cc_revision_idx', 'revision'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + line_no = Column('line_no', Unicode(10), nullable=True) + hl_lines = Column('hl_lines', Unicode(512), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', UnicodeText(25000), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_users(cls, revision=None, pull_request_id=None): + """ + Returns user associated with this ChangesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + q = Session().query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' + + STATUSES = [ + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), + ] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + # values for .status + STATUS_NEW = u'new' + STATUS_OPEN = u'open' + STATUS_CLOSED = u'closed' + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', UnicodeText(10240), nullable=True) + status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + @property + def org_ref_parts(self): + return self.org_ref.split(':') + + @property + def other_ref_parts(self): + return self.other_ref.split(':') + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers', + cascade="all, delete, delete-orphan") + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + statuses = relationship('ChangesetStatus') + comments = relationship('ChangesetComment', + cascade="all, delete, delete-orphan") + + def is_closed(self): + return self.status == self.STATUS_CLOSED + + @property + def last_review_status(self): + return self.statuses[-1].status if self.statuses else '' + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', UnicodeText(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self)\ + .order_by(UserNotification.user_id.asc()).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session().add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session().add(self) + + +class Gist(Base, BaseModel): + __tablename__ = 'gists' + __table_args__ = ( + Index('g_gist_access_id_idx', 'gist_access_id'), + Index('g_created_on_idx', 'created_on'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + GIST_PUBLIC = u'public' + GIST_PRIVATE = u'private' + + gist_id = Column('gist_id', Integer(), primary_key=True) + gist_access_id = Column('gist_access_id', Unicode(250)) + gist_description = Column('gist_description', UnicodeText(1024)) + gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) + gist_expires = Column('gist_expires', Float(53), nullable=False) + gist_type = Column('gist_type', Unicode(128), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + owner = relationship('User') + + @classmethod + def get_or_404(cls, id_): + res = cls.query().filter(cls.gist_access_id == id_).scalar() + if not res: + raise HTTPNotFound + return res + + @classmethod + def get_by_access_id(cls, gist_access_id): + return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() + + def gist_url(self): + import rhodecode + alias_url = rhodecode.CONFIG.get('gist_alias_url') + if alias_url: + return alias_url.replace('{gistid}', self.gist_access_id) + + from pylons import url + return url('gist', gist_id=self.gist_access_id, qualified=True) + + @classmethod + def base_path(cls): + """ + Returns base path when all gists are stored + + :param cls: + """ + from rhodecode.model.gist import GIST_STORE_LOC + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == URL_SEP) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return os.path.join(q.one().ui_value, GIST_STORE_LOC) + + def get_api_data(self): + """ + Common function for generating gist related data for API + """ + gist = self + data = dict( + gist_id=gist.gist_id, + type=gist.gist_type, + access_id=gist.gist_access_id, + description=gist.gist_description, + url=gist.gist_url(), + expires=gist.gist_expires, + created_on=gist.created_on, + ) + return data + + def __json__(self): + data = dict( + ) + data.update(self.get_api_data()) + return data + ## SCM functions + + @property + def scm_instance(self): + from rhodecode.lib.vcs import get_repo + base_path = self.base_path() + return get_repo(os.path.join(*map(safe_str, + [base_path, self.gist_access_id]))) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_2_0_1.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_2_0_1.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,2331 @@ +# -*- 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 . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import time +import logging +import datetime +import traceback +import hashlib +import collections +import functools + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError +from beaker.cache import cache_region, region_invalidate +from webob.exc import HTTPNotFound + +from pylons.i18n.translation import lazy_ugettext as _ + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.lib.vcs.backends.base import EmptyChangeset + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session + +URL_SEP = '/' +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +class BaseModel(object): + """ + Base Model for all classess + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """ + return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + + # also use __json__() if present to get additional fields + _json_attr = getattr(self, '__json__', None) + if _json_attr: + # update with attributes from __json__ + if callable(_json_attr): + _json_attr = _json_attr() + for k, val in _json_attr.iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session().query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def get_or_404(cls, id_): + try: + id_ = int(id_) + except (TypeError, ValueError): + raise HTTPNotFound + + res = cls.query().get(id_) + if not res: + raise HTTPNotFound + return res + + @classmethod + def getAll(cls): + # deprecated and left for backward compatibility + return cls.get_all() + + @classmethod + def get_all(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session().delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + try: + return safe_str(self.__unicode__()) + except UnicodeDecodeError: + pass + return '' % (self.__class__.__name__) + + +class RhodeCodeSetting(Base, BaseModel): + SETTINGS_TYPES = { + 'str': safe_str, + 'int': safe_int, + 'unicode': safe_unicode, + 'bool': str2bool, + 'list': functools.partial(aslist, sep=',') + } + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + _type = self.app_settings_type + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + return converter(v) + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + @hybrid_property + def app_settings_type(self): + return self._app_settings_type + + @app_settings_type.setter + def app_settings_type(self, val): + if val not in self.SETTINGS_TYPES: + raise Exception('type must be one of %s got %s' + % (self.SETTINGS_TYPES.keys(), val)) + self._app_settings_type = val + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value, self.app_settings_type + ) + + @classmethod + def get_by_name(cls, key): + return cls.query()\ + .filter(cls.app_settings_name == key).scalar() + + @classmethod + def get_by_name_or_create(cls, key, val='', type='unicode'): + res = cls.get_by_name(key) + if not res: + res = cls(key, val, type) + return res + + @classmethod + def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): + """ + Creates or updates RhodeCode setting. If updates is triggered it will only + update parameters that are explicityl set Optional instance will be skipped + + :param key: + :param val: + :param type: + :return: + """ + res = cls.get_by_name(key) + if not res: + val = Optional.extract(val) + type = Optional.extract(type) + res = cls(key, val, type) + else: + res.app_settings_name = key + if not isinstance(val, Optional): + # update if set + res.app_settings_value = val + if not isinstance(type, Optional): + # update if set + res.app_settings_type = type + return res + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + if cache: + ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = \ + each.app_settings_value + + return settings + + @classmethod + def get_auth_plugins(cls, cache=False): + auth_plugins = cls.get_by_name("auth_plugins").app_settings_value + return auth_plugins + + @classmethod + def get_auth_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('auth_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name: row.app_settings_value}) + + return fd + + @classmethod + def get_default_repo_settings(cls, cache=False, strip_prefix=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('default_')).all() + fd = {} + for row in ret: + key = row.app_settings_name + if strip_prefix: + key = remove_prefix(key, prefix='default_') + fd.update({key: row.app_settings_value}) + + return fd + + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': safe_unicode(platform.platform()), + 'rhodecode_version': rhodecode.__version__, + 'git_version': safe_unicode(check_git_version()), + 'git_path': rhodecode.CONFIG.get('git_path') + } + return info + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'changegroup.push_logger' + HOOK_PRE_PUSH = 'prechangegroup.pre_push' + HOOK_PULL = 'outgoing.pull_logger' + HOOK_PRE_PULL = 'preoutgoing.pre_pull' + + ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + # def __init__(self, section='', key='', value=''): + # self.ui_section = section + # self.ui_key = key + # self.ui_value = value + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key).scalar() + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def get_repos_location(cls): + return cls.get_by_key('/').ui_value + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key) or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session().add(new_ui) + + def __repr__(self): + return '' % (self.__class__.__name__, self.ui_key, + self.ui_value) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + DEFAULT_USER = 'default' + + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) + name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) + extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user_log = relationship('UserLog') + user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') + + repositories = relationship('Repository') + user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') + + repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UserGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + #extra emails for this user + user_emails = relationship('UserEmailMap', cascade='all') + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def firstname(self): + # alias for future + return self.name + + @property + def emails(self): + other = UserEmailMap.query().filter(UserEmailMap.user==self).all() + return [self.email] + [x.email for x in other] + + @property + def ip_addresses(self): + ret = UserIpMap.query().filter(UserIpMap.user == self).all() + return [x.ip_addr for x in ret] + + @property + def username_and_name(self): + return '%s (%s %s)' % (self.username, self.firstname, self.lastname) + + @property + def full_name(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.firstname, self.lastname) + if (self.firstname and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def is_admin(self): + return self.admin + + @property + def AuthUser(self): + """ + Returns instance of AuthUser for this user + """ + from rhodecode.lib.auth import AuthUser + return AuthUser(user_id=self.user_id, api_key=self.api_key, + username=self.username) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + return q.scalar() + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret + + @classmethod + def get_from_cs_author(cls, author): + """ + Tries to get User objects out of commit author string + + :param author: + """ + from rhodecode.lib.helpers import email, author_name + # Valid email in the attribute passed, see if they're in the system + _email = email(author) + if _email: + user = cls.get_by_email(_email, case_insensitive=True) + if user: + return user + # Maybe we can match by username? + _author = author_name(author) + user = cls.get_by_username(_author, case_insensitive=True) + if user: + return user + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session().add(self) + log.debug('updated user %s lastlogin' % self.username) + + @classmethod + def get_first_admin(cls): + user = User.query().filter(User.admin == True).first() + if user is None: + raise Exception('Missing administrative account!') + return user + + @classmethod + def get_default_user(cls, cache=False): + user = User.get_by_username(User.DEFAULT_USER, cache=cache) + if user is None: + raise Exception('Missing default account!') + return user + + def get_api_data(self): + """ + Common function for generating user related data for API + """ + user = self + data = dict( + user_id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + emails=user.emails, + api_key=user.api_key, + active=user.active, + admin=user.admin, + extern_type=user.extern_type, + extern_name=user.extern_name, + last_login=user.last_login, + ip_addresses=user.ip_addresses + ) + return data + + def __json__(self): + data = dict( + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + data.update(self.get_api_data()) + return data + + +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session().query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + +class UserIpMap(Base, BaseModel): + __tablename__ = 'user_ip_map' + __table_args__ = ( + UniqueConstraint('user_id', 'ip_addr'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + user = relationship('User', lazy='joined') + + @classmethod + def _get_ip_range(cls, ip_addr): + from rhodecode.lib import ipaddr + net = ipaddr.IPNetwork(address=ip_addr) + return [str(net.network), str(net.broadcast)] + + def __json__(self): + return dict( + ip_addr=self.ip_addr, + ip_range=self._get_ip_range(self.ip_addr) + ) + + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) + repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.repository_name, + self.action) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UserGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + users_group_to_perm = relationship('UserGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') + user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') + + user = relationship('User') + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.users_group_id, + self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, user_group_id, cache=False): + user_group = cls.query() + if cache: + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self + + data = dict( + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + group_description=user_group.user_group_description, + active=user_group.users_group_active, + owner=user_group.user.username, + ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members + + return data + + +class UserGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + + user = relationship('User', lazy='joined') + users_group = relationship('UserGroup') + + def __init__(self, gr_id='', u_id=''): + self.users_group_id = gr_id + self.user_id = u_id + + +class RepositoryField(Base, BaseModel): + __tablename__ = 'repositories_fields' + __table_args__ = ( + UniqueConstraint('repository_id', 'field_key'), # no-multi field + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields + + repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) + field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_type = Column("field_type", String(256), nullable=False, unique=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repository = relationship('Repository') + + @property + def field_key_prefixed(self): + return 'ex_%s' % self.field_key + + @classmethod + def un_prefix_key(cls, key): + if key.startswith(cls.PREFIX): + return key[len(cls.PREFIX):] + return key + + @classmethod + def get_by_key_name(cls, key, repo): + row = cls.query()\ + .filter(cls.repository == repo)\ + .filter(cls.field_key == key).scalar() + return row + + +class Repository(Base, BaseModel): + __tablename__ = 'repositories' + __table_args__ = ( + UniqueConstraint('repo_name'), + Index('r_repo_name_idx', 'repo_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + private = Column("private", Boolean(), nullable=True, unique=None, default=None) + enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) + enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) + description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data + + fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + stats = relationship('Statistics', cascade='all', uselist=False) + + followers = relationship('UserFollowing', + primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', + cascade='all') + extra_fields = relationship('RepositoryField', + cascade="all, delete, delete-orphan") + + logs = relationship('UserLog') + comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") + + pull_requests_org = relationship('PullRequest', + primaryjoin='PullRequest.org_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + pull_requests_other = relationship('PullRequest', + primaryjoin='PullRequest.other_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, + safe_unicode(self.repo_name)) + + @hybrid_property + def locked(self): + # always should return [user_id, timelocked] + if self._locked: + _lock_info = self._locked.split(':') + return int(_lock_info[0]), _lock_info[1] + return [None, None] + + @locked.setter + def locked(self, val): + if val and isinstance(val, (list, tuple)): + self._locked = ':'.join(map(str, val)) + else: + self._locked = None + + @hybrid_property + def changeset_cache(self): + from rhodecode.lib.vcs.backends.base import EmptyChangeset + dummy = EmptyChangeset().__json__() + if not self._changeset_cache: + return dummy + try: + return json.loads(self._changeset_cache) + except TypeError: + return dummy + + @changeset_cache.setter + def changeset_cache(self, val): + try: + self._changeset_cache = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def normalize_repo_name(cls, repo_name): + """ + Normalizes os specific repo_name to the format internally stored inside + dabatabase using URL_SEP + + :param cls: + :param repo_name: + """ + return cls.url_sep().join(repo_name.split(os.sep)) + + @classmethod + def get_by_repo_name(cls, repo_name): + q = Session().query(cls).filter(cls.repo_name == repo_name) + q = q.options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group)) + return q.scalar() + + @classmethod + def get_by_full_path(cls, repo_full_path): + repo_name = repo_full_path.split(cls.base_path(), 1)[-1] + repo_name = cls.normalize_repo_name(repo_name) + return cls.get_by_repo_name(repo_name.strip(URL_SEP)) + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + def groups_with_parents(self): + groups = [] + if self.group is None: + return groups + + cur_gr = self.group + groups.insert(0, cur_gr) + while 1: + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + groups.insert(0, gr) + + return groups + + @property + def groups_and_repo(self): + return self.groups_with_parents, self.just_name, self.repo_name + + @LazyProperty + def repo_path(self): + """ + Returns base full path for that repository means where it actually + exists on a filesystem + """ + q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def repo_full_path(self): + p = [self.repo_path] + # we need to split the name by / since this is how we store the + # names in the database, but that eventually needs to be converted + # into a valid system path + p += self.repo_name.split(Repository.url_sep()) + return os.path.join(*map(safe_unicode, p)) + + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + def _ui(self): + """ + Creates an db based ui object for this repository + """ + from rhodecode.lib.utils import make_ui + return make_ui('db', clear_session=False) + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + def get_api_data(self): + """ + Common function for generating repo api data + + """ + repo = self + data = dict( + repo_id=repo.repo_id, + repo_name=repo.repo_name, + repo_type=repo.repo_type, + clone_uri=repo.clone_uri, + private=repo.private, + created_on=repo.created_on, + description=repo.description, + landing_rev=repo.landing_rev, + owner=repo.user.username, + fork_of=repo.fork.repo_name if repo.fork else None, + enable_statistics=repo.enable_statistics, + enable_locking=repo.enable_locking, + enable_downloads=repo.enable_downloads, + last_changeset=repo.changeset_cache, + locked_by=User.get(self.locked[0]).get_api_data() \ + if self.locked[0] else None, + locked_date=time_to_datetime(self.locked[1]) \ + if self.locked[1] else None + ) + rc_config = RhodeCodeSetting.get_app_settings() + repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) + if repository_fields: + for f in self.extra_fields: + data[f.field_key_prefixed] = f.field_value + + return data + + @classmethod + def lock(cls, repo, user_id, lock_time=None): + if not lock_time: + lock_time = time.time() + repo.locked = [user_id, lock_time] + Session().add(repo) + Session().commit() + + @classmethod + def unlock(cls, repo): + repo.locked = None + Session().add(repo) + Session().commit() + + @classmethod + def getlock(cls, repo): + return repo.locked + + @property + def last_db_change(self): + return self.updated_on + + def clone_url(self, **override): + from pylons import url + from urlparse import urlparse + import urllib + parsed_url = urlparse(url('home', qualified=True)) + default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' + decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) + args = { + 'user': '', + 'pass': '', + 'scheme': parsed_url.scheme, + 'netloc': parsed_url.netloc, + 'prefix': decoded_path, + 'path': self.repo_name + } + + args.update(override) + return default_clone_uri % args + + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev=None): + return get_changeset_safe(self.scm_instance, rev) + + def get_landing_changeset(self): + """ + Returns landing changeset, or if that doesn't exist returns the tip + """ + cs = self.get_changeset(self.landing_rev) or self.get_changeset() + return cs + + def update_changeset_cache(self, cs_cache=None): + """ + Update cache of last changeset for repository, keys should be:: + + short_id + raw_id + revision + message + date + author + + :param cs_cache: + """ + from rhodecode.lib.vcs.backends.base import BaseChangeset + if cs_cache is None: + cs_cache = EmptyChangeset() + # use no-cache version here + scm_repo = self.scm_instance_no_cache() + if scm_repo: + cs_cache = scm_repo.get_changeset() + + if isinstance(cs_cache, BaseChangeset): + cs_cache = cs_cache.__json__() + + if (cs_cache != self.changeset_cache or not self.changeset_cache): + _default = datetime.datetime.fromtimestamp(0) + last_change = cs_cache.get('date') or _default + log.debug('updated repo %s with new cs cache %s' + % (self.repo_name, cs_cache)) + self.updated_on = last_change + self.changeset_cache = cs_cache + Session().add(self) + Session().commit() + else: + log.debug('Skipping repo:%s already with latest changes' + % self.repo_name) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author + + @property + def last_change(self): + return self.scm_instance.last_change + + def get_comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = collections.defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + + #maybe we have open new pullrequest without a status ? + stat = ChangesetStatus.STATUS_UNDER_REVIEW + status_lbl = ChangesetStatus.get_status_lbl(stat) + for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): + for rev in pr.revisions: + pr_id = pr.pull_request_id + pr_repo = pr.other_repo.repo_name + grouped[rev] = [stat, status_lbl, pr_id, pr_repo] + + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + + def _repo_size(self): + from rhodecode.lib import helpers as h + log.debug('calculating repository size...') + return h.format_byte_size(self.scm_instance.size) + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + def set_invalidate(self): + """ + Mark caches of this repo as invalid. + """ + CacheInvalidation.set_invalidate(self.repo_name) + + def scm_instance_no_cache(self): + return self.__get_instance() + + @property + def scm_instance(self): + import rhodecode + full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) + if full_cache: + return self.scm_instance_cached() + return self.__get_instance() + + def scm_instance_cached(self, valid_cache_keys=None): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + + valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) + if not valid: + log.debug('Cache for %s invalidated, getting new object' % (rn)) + region_invalidate(_c, None, rn) + else: + log.debug('Getting obj for %s from cache' % (rn)) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository from %s' + % (alias, repo_full_path)) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + log.error('Perhaps this repository is in db and not in ' + 'filesystem run rescan repositories with ' + '"destroy old data " option from admin panel') + return + + if alias == 'hg': + + repo = backend(safe_str(repo_full_path), create=False, + baseui=self._ui) + # skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + __mapper_args__ = {'order_by': 'group_name'} + + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + #TODO: create this field in migrations + #created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) + user = relationship('User') + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def groups_choices(cls, groups=None, show_empty_group=True): + from webhelpers.html import literal as _literal + if not groups: + groups = cls.query().all() + + repo_groups = [] + if show_empty_group: + repo_groups = [('-1', u'-- %s --' % _('top level'))] + sep = ' » ' + _name = lambda k: _literal(sep.join(k)) + + repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) + for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def _recursive_objects(self, include_repos=True): + all_ = [] + + def _get_members(root_gr): + if include_repos: + for r in root_gr.repositories: + all_.append(r) + childs = root_gr.children.all() + if childs: + for gr in childs: + all_.append(gr) + _get_members(gr) + + _get_members(self) + return [self] + all_ + + def recursive_groups_and_repos(self): + """ + Recursive return all groups, with repositories in those groups + """ + return self._recursive_objects() + + def recursive_groups(self): + """ + Returns all children groups for this group including children of children + """ + return self._recursive_objects(include_repos=False) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PERMS = [ + ('hg.admin', _('RhodeCode Administrator')), + + ('repository.none', _('Repository no access')), + ('repository.read', _('Repository read access')), + ('repository.write', _('Repository write access')), + ('repository.admin', _('Repository admin access')), + + ('group.none', _('Repository group no access')), + ('group.read', _('Repository group read access')), + ('group.write', _('Repository group write access')), + ('group.admin', _('Repository group admin access')), + + ('usergroup.none', _('User group no access')), + ('usergroup.read', _('User group read access')), + ('usergroup.write', _('User group write access')), + ('usergroup.admin', _('User group admin access')), + + ('hg.repogroup.create.false', _('Repository Group creation disabled')), + ('hg.repogroup.create.true', _('Repository Group creation enabled')), + + ('hg.usergroup.create.false', _('User Group creation disabled')), + ('hg.usergroup.create.true', _('User Group creation enabled')), + + ('hg.create.none', _('Repository creation disabled')), + ('hg.create.repository', _('Repository creation enabled')), + + ('hg.fork.none', _('Repository forking disabled')), + ('hg.fork.repository', _('Repository forking enabled')), + + ('hg.register.none', _('Registration disabled')), + ('hg.register.manual_activate', _('User Registration with manual account activation')), + ('hg.register.auto_activate', _('User Registration with automatic account activation')), + + ('hg.extern_activate.manual', _('Manual activation of external account')), + ('hg.extern_activate.auto', _('Automatic activation of external account')), + + ] + + #definition of system default permissions for DEFAULT user + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'usergroup.read', + 'hg.create.repository', + 'hg.fork.repository', + 'hg.register.manual_activate', + 'hg.extern_activate.auto', + ] + + # defines which permissions are more important higher the more important + # Weight defines which permissions are more important. + # The higher number the more important. + PERM_WEIGHTS = { + 'repository.none': 0, + 'repository.read': 1, + 'repository.write': 3, + 'repository.admin': 4, + + 'group.none': 0, + 'group.read': 1, + 'group.write': 3, + 'group.admin': 4, + + 'usergroup.none': 0, + 'usergroup.read': 1, + 'usergroup.write': 3, + 'usergroup.admin': 4, + 'hg.repogroup.create.false': 0, + 'hg.repogroup.create.true': 1, + + 'hg.usergroup.create.false': 0, + 'hg.usergroup.create.true': 1, + + 'hg.fork.none': 0, + 'hg.fork.repository': 1, + 'hg.create.none': 0, + 'hg.create.repository': 1 + } + + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session().query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_user_group_perms(cls, default_user_id): + q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ + .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ + .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserUserGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.repository) + + +class UserUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'user_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + user_group = relationship('UserGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, user_group, permission): + n = cls() + n.user = user + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.user_group) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.permission) + + +class UserGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) + + +class UserGroupUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_group_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), + CheckConstraint('target_user_group_id != user_group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') + user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') + permission = relationship('Permission') + + @classmethod + def create(cls, target_user_group, user_group, permission): + n = cls() + n.target_user_group = target_user_group + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.target_user_group, self.user_group) + + +class UserGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + +class UserGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + Index('key_idx', 'cache_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + # cache_id, not used + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + # cache_key as created by _get_cache_key + cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # cache_args is a repo_name + cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # instance sets cache_active True when it is caching, + # other instances set cache_active to False to indicate that this cache is invalid + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, repo_name=''): + self.cache_key = cache_key + self.cache_args = repo_name + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, + self.cache_id, self.cache_key, self.cache_active) + + def _cache_key_partition(self): + prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) + return prefix, repo_name, suffix + + def get_prefix(self): + """ + get prefix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[0] + + def get_suffix(self): + """ + get suffix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[2] + + @classmethod + def clear_cache(cls): + """ + Delete all cache keys from database. + Should only be run when all instances are down and all entries thus stale. + """ + cls.query().delete() + Session().commit() + + @classmethod + def _get_cache_key(cls, key): + """ + Wrapper for generating a unique cache key for this instance and "key". + key must / will start with a repo_name which will be stored in .cache_args . + """ + import rhodecode + prefix = rhodecode.CONFIG.get('instance_id', '') + return "%s%s" % (prefix, key) + + @classmethod + def set_invalidate(cls, repo_name, delete=False): + """ + Mark all caches of a repo as invalid in the database. + """ + inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() + + try: + for inv_obj in inv_objs: + log.debug('marking %s key for invalidation based on repo_name=%s' + % (inv_obj, safe_str(repo_name))) + if delete: + Session().delete(inv_obj) + else: + inv_obj.cache_active = False + Session().add(inv_obj) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + + @classmethod + def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): + """ + Mark this cache key as active and currently cached. + Return True if the existing cache registration still was valid. + Return False to indicate that it had been invalidated and caches should be refreshed. + """ + + key = (repo_name + '_' + kind) if kind else repo_name + cache_key = cls._get_cache_key(key) + + if valid_cache_keys and cache_key in valid_cache_keys: + return True + + try: + inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() + if not inv_obj: + inv_obj = CacheInvalidation(cache_key, repo_name) + was_valid = inv_obj.cache_active + inv_obj.cache_active = True + Session().add(inv_obj) + Session().commit() + return was_valid + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + return False + + @classmethod + def get_valid_cache_keys(cls): + """ + Return opaque object with information of which caches still are valid + and can be used without checking for invalidation. + """ + return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + Index('cc_revision_idx', 'revision'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + line_no = Column('line_no', Unicode(10), nullable=True) + hl_lines = Column('hl_lines', Unicode(512), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', UnicodeText(25000), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_users(cls, revision=None, pull_request_id=None): + """ + Returns user associated with this ChangesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + q = Session().query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' + + STATUSES = [ + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), + ] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + # values for .status + STATUS_NEW = u'new' + STATUS_OPEN = u'open' + STATUS_CLOSED = u'closed' + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', UnicodeText(10240), nullable=True) + status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + @property + def org_ref_parts(self): + return self.org_ref.split(':') + + @property + def other_ref_parts(self): + return self.other_ref.split(':') + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers', + cascade="all, delete, delete-orphan") + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + statuses = relationship('ChangesetStatus') + comments = relationship('ChangesetComment', + cascade="all, delete, delete-orphan") + + def is_closed(self): + return self.status == self.STATUS_CLOSED + + @property + def last_review_status(self): + return self.statuses[-1].status if self.statuses else '' + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', UnicodeText(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self)\ + .order_by(UserNotification.user_id.asc()).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session().add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session().add(self) + + +class Gist(Base, BaseModel): + __tablename__ = 'gists' + __table_args__ = ( + Index('g_gist_access_id_idx', 'gist_access_id'), + Index('g_created_on_idx', 'created_on'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + GIST_PUBLIC = u'public' + GIST_PRIVATE = u'private' + + gist_id = Column('gist_id', Integer(), primary_key=True) + gist_access_id = Column('gist_access_id', Unicode(250)) + gist_description = Column('gist_description', UnicodeText(1024)) + gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) + gist_expires = Column('gist_expires', Float(53), nullable=False) + gist_type = Column('gist_type', Unicode(128), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + owner = relationship('User') + + @classmethod + def get_or_404(cls, id_): + res = cls.query().filter(cls.gist_access_id == id_).scalar() + if not res: + raise HTTPNotFound + return res + + @classmethod + def get_by_access_id(cls, gist_access_id): + return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() + + def gist_url(self): + import rhodecode + alias_url = rhodecode.CONFIG.get('gist_alias_url') + if alias_url: + return alias_url.replace('{gistid}', self.gist_access_id) + + from pylons import url + return url('gist', gist_id=self.gist_access_id, qualified=True) + + @classmethod + def base_path(cls): + """ + Returns base path when all gists are stored + + :param cls: + """ + from rhodecode.model.gist import GIST_STORE_LOC + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == URL_SEP) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return os.path.join(q.one().ui_value, GIST_STORE_LOC) + + def get_api_data(self): + """ + Common function for generating gist related data for API + """ + gist = self + data = dict( + gist_id=gist.gist_id, + type=gist.gist_type, + access_id=gist.gist_access_id, + description=gist.gist_description, + url=gist.gist_url(), + expires=gist.gist_expires, + created_on=gist.created_on, + ) + return data + + def __json__(self): + data = dict( + ) + data.update(self.get_api_data()) + return data + ## SCM functions + + @property + def scm_instance(self): + from rhodecode.lib.vcs import get_repo + base_path = self.base_path() + return get_repo(os.path.join(*map(safe_str, + [base_path, self.gist_access_id]))) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_2_0_2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_2_0_2.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,2352 @@ +# -*- 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 . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import time +import logging +import datetime +import traceback +import hashlib +import collections +import functools + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError +from beaker.cache import cache_region, region_invalidate +from webob.exc import HTTPNotFound + +from pylons.i18n.translation import lazy_ugettext as _ + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.lib.vcs.backends.base import EmptyChangeset + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session + +URL_SEP = '/' +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +class BaseModel(object): + """ + Base Model for all classess + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """ + return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + + # also use __json__() if present to get additional fields + _json_attr = getattr(self, '__json__', None) + if _json_attr: + # update with attributes from __json__ + if callable(_json_attr): + _json_attr = _json_attr() + for k, val in _json_attr.iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session().query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def get_or_404(cls, id_): + try: + id_ = int(id_) + except (TypeError, ValueError): + raise HTTPNotFound + + res = cls.query().get(id_) + if not res: + raise HTTPNotFound + return res + + @classmethod + def getAll(cls): + # deprecated and left for backward compatibility + return cls.get_all() + + @classmethod + def get_all(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session().delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + try: + return safe_str(self.__unicode__()) + except UnicodeDecodeError: + pass + return '' % (self.__class__.__name__) + + +class RhodeCodeSetting(Base, BaseModel): + SETTINGS_TYPES = { + 'str': safe_str, + 'int': safe_int, + 'unicode': safe_unicode, + 'bool': str2bool, + 'list': functools.partial(aslist, sep=',') + } + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + _type = self.app_settings_type + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + return converter(v) + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + @hybrid_property + def app_settings_type(self): + return self._app_settings_type + + @app_settings_type.setter + def app_settings_type(self, val): + if val not in self.SETTINGS_TYPES: + raise Exception('type must be one of %s got %s' + % (self.SETTINGS_TYPES.keys(), val)) + self._app_settings_type = val + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value, self.app_settings_type + ) + + @classmethod + def get_by_name(cls, key): + return cls.query()\ + .filter(cls.app_settings_name == key).scalar() + + @classmethod + def get_by_name_or_create(cls, key, val='', type='unicode'): + res = cls.get_by_name(key) + if not res: + res = cls(key, val, type) + return res + + @classmethod + def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): + """ + Creates or updates RhodeCode setting. If updates is triggered it will only + update parameters that are explicityl set Optional instance will be skipped + + :param key: + :param val: + :param type: + :return: + """ + res = cls.get_by_name(key) + if not res: + val = Optional.extract(val) + type = Optional.extract(type) + res = cls(key, val, type) + else: + res.app_settings_name = key + if not isinstance(val, Optional): + # update if set + res.app_settings_value = val + if not isinstance(type, Optional): + # update if set + res.app_settings_type = type + return res + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + if cache: + ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = \ + each.app_settings_value + + return settings + + @classmethod + def get_auth_plugins(cls, cache=False): + auth_plugins = cls.get_by_name("auth_plugins").app_settings_value + return auth_plugins + + @classmethod + def get_auth_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('auth_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name: row.app_settings_value}) + + return fd + + @classmethod + def get_default_repo_settings(cls, cache=False, strip_prefix=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('default_')).all() + fd = {} + for row in ret: + key = row.app_settings_name + if strip_prefix: + key = remove_prefix(key, prefix='default_') + fd.update({key: row.app_settings_value}) + + return fd + + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': safe_unicode(platform.platform()), + 'rhodecode_version': rhodecode.__version__, + 'git_version': safe_unicode(check_git_version()), + 'git_path': rhodecode.CONFIG.get('git_path') + } + return info + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'changegroup.push_logger' + HOOK_PRE_PUSH = 'prechangegroup.pre_push' + HOOK_PULL = 'outgoing.pull_logger' + HOOK_PRE_PULL = 'preoutgoing.pre_pull' + + ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + # def __init__(self, section='', key='', value=''): + # self.ui_section = section + # self.ui_key = key + # self.ui_value = value + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key).scalar() + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def get_repos_location(cls): + return cls.get_by_key('/').ui_value + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key) or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session().add(new_ui) + + def __repr__(self): + return '' % (self.__class__.__name__, self.ui_key, + self.ui_value) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + DEFAULT_USER = 'default' + + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) + name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) + extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user_log = relationship('UserLog') + user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') + + repositories = relationship('Repository') + user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') + + repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UserGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + #extra emails for this user + user_emails = relationship('UserEmailMap', cascade='all') + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def firstname(self): + # alias for future + return self.name + + @property + def emails(self): + other = UserEmailMap.query().filter(UserEmailMap.user==self).all() + return [self.email] + [x.email for x in other] + + @property + def ip_addresses(self): + ret = UserIpMap.query().filter(UserIpMap.user == self).all() + return [x.ip_addr for x in ret] + + @property + def username_and_name(self): + return '%s (%s %s)' % (self.username, self.firstname, self.lastname) + + @property + def full_name(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.firstname, self.lastname) + if (self.firstname and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def is_admin(self): + return self.admin + + @property + def AuthUser(self): + """ + Returns instance of AuthUser for this user + """ + from rhodecode.lib.auth import AuthUser + return AuthUser(user_id=self.user_id, api_key=self.api_key, + username=self.username) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + return q.scalar() + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret + + @classmethod + def get_from_cs_author(cls, author): + """ + Tries to get User objects out of commit author string + + :param author: + """ + from rhodecode.lib.helpers import email, author_name + # Valid email in the attribute passed, see if they're in the system + _email = email(author) + if _email: + user = cls.get_by_email(_email, case_insensitive=True) + if user: + return user + # Maybe we can match by username? + _author = author_name(author) + user = cls.get_by_username(_author, case_insensitive=True) + if user: + return user + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session().add(self) + log.debug('updated user %s lastlogin' % self.username) + + @classmethod + def get_first_admin(cls): + user = User.query().filter(User.admin == True).first() + if user is None: + raise Exception('Missing administrative account!') + return user + + @classmethod + def get_default_user(cls, cache=False): + user = User.get_by_username(User.DEFAULT_USER, cache=cache) + if user is None: + raise Exception('Missing default account!') + return user + + def get_api_data(self): + """ + Common function for generating user related data for API + """ + user = self + data = dict( + user_id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + emails=user.emails, + api_key=user.api_key, + active=user.active, + admin=user.admin, + extern_type=user.extern_type, + extern_name=user.extern_name, + last_login=user.last_login, + ip_addresses=user.ip_addresses + ) + return data + + def __json__(self): + data = dict( + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + data.update(self.get_api_data()) + return data + + +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session().query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + +class UserIpMap(Base, BaseModel): + __tablename__ = 'user_ip_map' + __table_args__ = ( + UniqueConstraint('user_id', 'ip_addr'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + user = relationship('User', lazy='joined') + + @classmethod + def _get_ip_range(cls, ip_addr): + from rhodecode.lib import ipaddr + net = ipaddr.IPNetwork(address=ip_addr) + return [str(net.network), str(net.broadcast)] + + def __json__(self): + return dict( + ip_addr=self.ip_addr, + ip_range=self._get_ip_range(self.ip_addr) + ) + + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) + repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.repository_name, + self.action) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UserGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + users_group_to_perm = relationship('UserGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') + user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') + + user = relationship('User') + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.users_group_id, + self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, user_group_id, cache=False): + user_group = cls.query() + if cache: + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self + + data = dict( + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + group_description=user_group.user_group_description, + active=user_group.users_group_active, + owner=user_group.user.username, + ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members + + return data + + +class UserGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + + user = relationship('User', lazy='joined') + users_group = relationship('UserGroup') + + def __init__(self, gr_id='', u_id=''): + self.users_group_id = gr_id + self.user_id = u_id + + +class RepositoryField(Base, BaseModel): + __tablename__ = 'repositories_fields' + __table_args__ = ( + UniqueConstraint('repository_id', 'field_key'), # no-multi field + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields + + repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) + field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_type = Column("field_type", String(256), nullable=False, unique=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repository = relationship('Repository') + + @property + def field_key_prefixed(self): + return 'ex_%s' % self.field_key + + @classmethod + def un_prefix_key(cls, key): + if key.startswith(cls.PREFIX): + return key[len(cls.PREFIX):] + return key + + @classmethod + def get_by_key_name(cls, key, repo): + row = cls.query()\ + .filter(cls.repository == repo)\ + .filter(cls.field_key == key).scalar() + return row + + +class Repository(Base, BaseModel): + __tablename__ = 'repositories' + __table_args__ = ( + UniqueConstraint('repo_name'), + Index('r_repo_name_idx', 'repo_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + private = Column("private", Boolean(), nullable=True, unique=None, default=None) + enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) + enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) + description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data + + fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + stats = relationship('Statistics', cascade='all', uselist=False) + + followers = relationship('UserFollowing', + primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', + cascade='all') + extra_fields = relationship('RepositoryField', + cascade="all, delete, delete-orphan") + + logs = relationship('UserLog') + comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") + + pull_requests_org = relationship('PullRequest', + primaryjoin='PullRequest.org_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + pull_requests_other = relationship('PullRequest', + primaryjoin='PullRequest.other_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, + safe_unicode(self.repo_name)) + + @hybrid_property + def landing_rev(self): + # always should return [rev_type, rev] + if self._landing_revision: + _rev_info = self._landing_revision.split(':') + if len(_rev_info) < 2: + _rev_info.insert(0, 'rev') + return [_rev_info[0], _rev_info[1]] + return [None, None] + + @landing_rev.setter + def landing_rev(self, val): + if ':' not in val: + raise ValueError('value must be delimited with `:` and consist ' + 'of :, got %s instead' % val) + self._landing_revision = val + + @hybrid_property + def locked(self): + # always should return [user_id, timelocked] + if self._locked: + _lock_info = self._locked.split(':') + return int(_lock_info[0]), _lock_info[1] + return [None, None] + + @locked.setter + def locked(self, val): + if val and isinstance(val, (list, tuple)): + self._locked = ':'.join(map(str, val)) + else: + self._locked = None + + @hybrid_property + def changeset_cache(self): + from rhodecode.lib.vcs.backends.base import EmptyChangeset + dummy = EmptyChangeset().__json__() + if not self._changeset_cache: + return dummy + try: + return json.loads(self._changeset_cache) + except TypeError: + return dummy + + @changeset_cache.setter + def changeset_cache(self, val): + try: + self._changeset_cache = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def normalize_repo_name(cls, repo_name): + """ + Normalizes os specific repo_name to the format internally stored inside + dabatabase using URL_SEP + + :param cls: + :param repo_name: + """ + return cls.url_sep().join(repo_name.split(os.sep)) + + @classmethod + def get_by_repo_name(cls, repo_name): + q = Session().query(cls).filter(cls.repo_name == repo_name) + q = q.options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group)) + return q.scalar() + + @classmethod + def get_by_full_path(cls, repo_full_path): + repo_name = repo_full_path.split(cls.base_path(), 1)[-1] + repo_name = cls.normalize_repo_name(repo_name) + return cls.get_by_repo_name(repo_name.strip(URL_SEP)) + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + def groups_with_parents(self): + groups = [] + if self.group is None: + return groups + + cur_gr = self.group + groups.insert(0, cur_gr) + while 1: + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + groups.insert(0, gr) + + return groups + + @property + def groups_and_repo(self): + return self.groups_with_parents, self.just_name, self.repo_name + + @LazyProperty + def repo_path(self): + """ + Returns base full path for that repository means where it actually + exists on a filesystem + """ + q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def repo_full_path(self): + p = [self.repo_path] + # we need to split the name by / since this is how we store the + # names in the database, but that eventually needs to be converted + # into a valid system path + p += self.repo_name.split(Repository.url_sep()) + return os.path.join(*map(safe_unicode, p)) + + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + def _ui(self): + """ + Creates an db based ui object for this repository + """ + from rhodecode.lib.utils import make_ui + return make_ui('db', clear_session=False) + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + def get_api_data(self): + """ + Common function for generating repo api data + + """ + repo = self + data = dict( + repo_id=repo.repo_id, + repo_name=repo.repo_name, + repo_type=repo.repo_type, + clone_uri=repo.clone_uri, + private=repo.private, + created_on=repo.created_on, + description=repo.description, + landing_rev=repo.landing_rev, + owner=repo.user.username, + fork_of=repo.fork.repo_name if repo.fork else None, + enable_statistics=repo.enable_statistics, + enable_locking=repo.enable_locking, + enable_downloads=repo.enable_downloads, + last_changeset=repo.changeset_cache, + locked_by=User.get(self.locked[0]).get_api_data() \ + if self.locked[0] else None, + locked_date=time_to_datetime(self.locked[1]) \ + if self.locked[1] else None + ) + rc_config = RhodeCodeSetting.get_app_settings() + repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) + if repository_fields: + for f in self.extra_fields: + data[f.field_key_prefixed] = f.field_value + + return data + + @classmethod + def lock(cls, repo, user_id, lock_time=None): + if not lock_time: + lock_time = time.time() + repo.locked = [user_id, lock_time] + Session().add(repo) + Session().commit() + + @classmethod + def unlock(cls, repo): + repo.locked = None + Session().add(repo) + Session().commit() + + @classmethod + def getlock(cls, repo): + return repo.locked + + @property + def last_db_change(self): + return self.updated_on + + def clone_url(self, **override): + from pylons import url + from urlparse import urlparse + import urllib + parsed_url = urlparse(url('home', qualified=True)) + default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' + decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) + args = { + 'user': '', + 'pass': '', + 'scheme': parsed_url.scheme, + 'netloc': parsed_url.netloc, + 'prefix': decoded_path, + 'path': self.repo_name + } + + args.update(override) + return default_clone_uri % args + + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev=None): + return get_changeset_safe(self.scm_instance, rev) + + def get_landing_changeset(self): + """ + Returns landing changeset, or if that doesn't exist returns the tip + """ + _rev_type, _rev = self.landing_rev + cs = self.get_changeset(_rev) + if isinstance(cs, EmptyChangeset): + return self.get_changeset() + return cs + + def update_changeset_cache(self, cs_cache=None): + """ + Update cache of last changeset for repository, keys should be:: + + short_id + raw_id + revision + message + date + author + + :param cs_cache: + """ + from rhodecode.lib.vcs.backends.base import BaseChangeset + if cs_cache is None: + cs_cache = EmptyChangeset() + # use no-cache version here + scm_repo = self.scm_instance_no_cache() + if scm_repo: + cs_cache = scm_repo.get_changeset() + + if isinstance(cs_cache, BaseChangeset): + cs_cache = cs_cache.__json__() + + if (cs_cache != self.changeset_cache or not self.changeset_cache): + _default = datetime.datetime.fromtimestamp(0) + last_change = cs_cache.get('date') or _default + log.debug('updated repo %s with new cs cache %s' + % (self.repo_name, cs_cache)) + self.updated_on = last_change + self.changeset_cache = cs_cache + Session().add(self) + Session().commit() + else: + log.debug('Skipping repo:%s already with latest changes' + % self.repo_name) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author + + @property + def last_change(self): + return self.scm_instance.last_change + + def get_comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = collections.defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + + #maybe we have open new pullrequest without a status ? + stat = ChangesetStatus.STATUS_UNDER_REVIEW + status_lbl = ChangesetStatus.get_status_lbl(stat) + for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): + for rev in pr.revisions: + pr_id = pr.pull_request_id + pr_repo = pr.other_repo.repo_name + grouped[rev] = [stat, status_lbl, pr_id, pr_repo] + + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + + def _repo_size(self): + from rhodecode.lib import helpers as h + log.debug('calculating repository size...') + return h.format_byte_size(self.scm_instance.size) + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + def set_invalidate(self): + """ + Mark caches of this repo as invalid. + """ + CacheInvalidation.set_invalidate(self.repo_name) + + def scm_instance_no_cache(self): + return self.__get_instance() + + @property + def scm_instance(self): + import rhodecode + full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) + if full_cache: + return self.scm_instance_cached() + return self.__get_instance() + + def scm_instance_cached(self, valid_cache_keys=None): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + + valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) + if not valid: + log.debug('Cache for %s invalidated, getting new object' % (rn)) + region_invalidate(_c, None, rn) + else: + log.debug('Getting obj for %s from cache' % (rn)) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository from %s' + % (alias, repo_full_path)) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + log.error('Perhaps this repository is in db and not in ' + 'filesystem run rescan repositories with ' + '"destroy old data " option from admin panel') + return + + if alias == 'hg': + + repo = backend(safe_str(repo_full_path), create=False, + baseui=self._ui) + # skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + def __json__(self): + return dict(landing_rev = self.landing_rev) + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + __mapper_args__ = {'order_by': 'group_name'} + + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) + user = relationship('User') + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def groups_choices(cls, groups=None, show_empty_group=True): + from webhelpers.html import literal as _literal + if not groups: + groups = cls.query().all() + + repo_groups = [] + if show_empty_group: + repo_groups = [('-1', u'-- %s --' % _('top level'))] + sep = ' » ' + _name = lambda k: _literal(sep.join(k)) + + repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) + for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def _recursive_objects(self, include_repos=True): + all_ = [] + + def _get_members(root_gr): + if include_repos: + for r in root_gr.repositories: + all_.append(r) + childs = root_gr.children.all() + if childs: + for gr in childs: + all_.append(gr) + _get_members(gr) + + _get_members(self) + return [self] + all_ + + def recursive_groups_and_repos(self): + """ + Recursive return all groups, with repositories in those groups + """ + return self._recursive_objects() + + def recursive_groups(self): + """ + Returns all children groups for this group including children of children + """ + return self._recursive_objects(include_repos=False) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PERMS = [ + ('hg.admin', _('RhodeCode Administrator')), + + ('repository.none', _('Repository no access')), + ('repository.read', _('Repository read access')), + ('repository.write', _('Repository write access')), + ('repository.admin', _('Repository admin access')), + + ('group.none', _('Repository group no access')), + ('group.read', _('Repository group read access')), + ('group.write', _('Repository group write access')), + ('group.admin', _('Repository group admin access')), + + ('usergroup.none', _('User group no access')), + ('usergroup.read', _('User group read access')), + ('usergroup.write', _('User group write access')), + ('usergroup.admin', _('User group admin access')), + + ('hg.repogroup.create.false', _('Repository Group creation disabled')), + ('hg.repogroup.create.true', _('Repository Group creation enabled')), + + ('hg.usergroup.create.false', _('User Group creation disabled')), + ('hg.usergroup.create.true', _('User Group creation enabled')), + + ('hg.create.none', _('Repository creation disabled')), + ('hg.create.repository', _('Repository creation enabled')), + + ('hg.fork.none', _('Repository forking disabled')), + ('hg.fork.repository', _('Repository forking enabled')), + + ('hg.register.none', _('Registration disabled')), + ('hg.register.manual_activate', _('User Registration with manual account activation')), + ('hg.register.auto_activate', _('User Registration with automatic account activation')), + + ('hg.extern_activate.manual', _('Manual activation of external account')), + ('hg.extern_activate.auto', _('Automatic activation of external account')), + + ] + + #definition of system default permissions for DEFAULT user + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'usergroup.read', + 'hg.create.repository', + 'hg.fork.repository', + 'hg.register.manual_activate', + 'hg.extern_activate.auto', + ] + + # defines which permissions are more important higher the more important + # Weight defines which permissions are more important. + # The higher number the more important. + PERM_WEIGHTS = { + 'repository.none': 0, + 'repository.read': 1, + 'repository.write': 3, + 'repository.admin': 4, + + 'group.none': 0, + 'group.read': 1, + 'group.write': 3, + 'group.admin': 4, + + 'usergroup.none': 0, + 'usergroup.read': 1, + 'usergroup.write': 3, + 'usergroup.admin': 4, + 'hg.repogroup.create.false': 0, + 'hg.repogroup.create.true': 1, + + 'hg.usergroup.create.false': 0, + 'hg.usergroup.create.true': 1, + + 'hg.fork.none': 0, + 'hg.fork.repository': 1, + 'hg.create.none': 0, + 'hg.create.repository': 1 + } + + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session().query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_user_group_perms(cls, default_user_id): + q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ + .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ + .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserUserGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.repository) + + +class UserUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'user_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + user_group = relationship('UserGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, user_group, permission): + n = cls() + n.user = user + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.user_group) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.permission) + + +class UserGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) + + +class UserGroupUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_group_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), + CheckConstraint('target_user_group_id != user_group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') + user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') + permission = relationship('Permission') + + @classmethod + def create(cls, target_user_group, user_group, permission): + n = cls() + n.target_user_group = target_user_group + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.target_user_group, self.user_group) + + +class UserGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + +class UserGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + Index('key_idx', 'cache_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + # cache_id, not used + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + # cache_key as created by _get_cache_key + cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # cache_args is a repo_name + cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # instance sets cache_active True when it is caching, + # other instances set cache_active to False to indicate that this cache is invalid + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, repo_name=''): + self.cache_key = cache_key + self.cache_args = repo_name + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, + self.cache_id, self.cache_key, self.cache_active) + + def _cache_key_partition(self): + prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) + return prefix, repo_name, suffix + + def get_prefix(self): + """ + get prefix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[0] + + def get_suffix(self): + """ + get suffix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[2] + + @classmethod + def clear_cache(cls): + """ + Delete all cache keys from database. + Should only be run when all instances are down and all entries thus stale. + """ + cls.query().delete() + Session().commit() + + @classmethod + def _get_cache_key(cls, key): + """ + Wrapper for generating a unique cache key for this instance and "key". + key must / will start with a repo_name which will be stored in .cache_args . + """ + import rhodecode + prefix = rhodecode.CONFIG.get('instance_id', '') + return "%s%s" % (prefix, key) + + @classmethod + def set_invalidate(cls, repo_name, delete=False): + """ + Mark all caches of a repo as invalid in the database. + """ + inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() + + try: + for inv_obj in inv_objs: + log.debug('marking %s key for invalidation based on repo_name=%s' + % (inv_obj, safe_str(repo_name))) + if delete: + Session().delete(inv_obj) + else: + inv_obj.cache_active = False + Session().add(inv_obj) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + + @classmethod + def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): + """ + Mark this cache key as active and currently cached. + Return True if the existing cache registration still was valid. + Return False to indicate that it had been invalidated and caches should be refreshed. + """ + + key = (repo_name + '_' + kind) if kind else repo_name + cache_key = cls._get_cache_key(key) + + if valid_cache_keys and cache_key in valid_cache_keys: + return True + + try: + inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() + if not inv_obj: + inv_obj = CacheInvalidation(cache_key, repo_name) + was_valid = inv_obj.cache_active + inv_obj.cache_active = True + Session().add(inv_obj) + Session().commit() + return was_valid + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + return False + + @classmethod + def get_valid_cache_keys(cls): + """ + Return opaque object with information of which caches still are valid + and can be used without checking for invalidation. + """ + return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + Index('cc_revision_idx', 'revision'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + line_no = Column('line_no', Unicode(10), nullable=True) + hl_lines = Column('hl_lines', Unicode(512), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', UnicodeText(25000), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_users(cls, revision=None, pull_request_id=None): + """ + Returns user associated with this ChangesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + q = Session().query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' + + STATUSES = [ + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), + ] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + # values for .status + STATUS_NEW = u'new' + STATUS_OPEN = u'open' + STATUS_CLOSED = u'closed' + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', UnicodeText(10240), nullable=True) + status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + @property + def org_ref_parts(self): + return self.org_ref.split(':') + + @property + def other_ref_parts(self): + return self.other_ref.split(':') + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers', + cascade="all, delete, delete-orphan") + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + statuses = relationship('ChangesetStatus') + comments = relationship('ChangesetComment', + cascade="all, delete, delete-orphan") + + def is_closed(self): + return self.status == self.STATUS_CLOSED + + @property + def last_review_status(self): + return self.statuses[-1].status if self.statuses else '' + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', UnicodeText(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self)\ + .order_by(UserNotification.user_id.asc()).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session().add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session().add(self) + + +class Gist(Base, BaseModel): + __tablename__ = 'gists' + __table_args__ = ( + Index('g_gist_access_id_idx', 'gist_access_id'), + Index('g_created_on_idx', 'created_on'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + GIST_PUBLIC = u'public' + GIST_PRIVATE = u'private' + + gist_id = Column('gist_id', Integer(), primary_key=True) + gist_access_id = Column('gist_access_id', Unicode(250)) + gist_description = Column('gist_description', UnicodeText(1024)) + gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) + gist_expires = Column('gist_expires', Float(53), nullable=False) + gist_type = Column('gist_type', Unicode(128), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + owner = relationship('User') + + @classmethod + def get_or_404(cls, id_): + res = cls.query().filter(cls.gist_access_id == id_).scalar() + if not res: + raise HTTPNotFound + return res + + @classmethod + def get_by_access_id(cls, gist_access_id): + return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() + + def gist_url(self): + import rhodecode + alias_url = rhodecode.CONFIG.get('gist_alias_url') + if alias_url: + return alias_url.replace('{gistid}', self.gist_access_id) + + from pylons import url + return url('gist', gist_id=self.gist_access_id, qualified=True) + + @classmethod + def base_path(cls): + """ + Returns base path when all gists are stored + + :param cls: + """ + from rhodecode.model.gist import GIST_STORE_LOC + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == URL_SEP) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return os.path.join(q.one().ui_value, GIST_STORE_LOC) + + def get_api_data(self): + """ + Common function for generating gist related data for API + """ + gist = self + data = dict( + gist_id=gist.gist_id, + type=gist.gist_type, + access_id=gist.gist_access_id, + description=gist.gist_description, + url=gist.gist_url(), + expires=gist.gist_expires, + created_on=gist.created_on, + ) + return data + + def __json__(self): + data = dict( + ) + data.update(self.get_api_data()) + return data + ## SCM functions + + @property + def scm_instance(self): + from rhodecode.lib.vcs import get_repo + base_path = self.base_path() + return get_repo(os.path.join(*map(safe_str, + [base_path, self.gist_access_id]))) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_2_1_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_2_1_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,2391 @@ +# -*- 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 . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import time +import logging +import datetime +import traceback +import hashlib +import collections +import functools + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError +from beaker.cache import cache_region, region_invalidate +from webob.exc import HTTPNotFound + +from pylons.i18n.translation import lazy_ugettext as _ + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.lib.vcs.backends.base import EmptyChangeset + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session + +URL_SEP = '/' +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +class BaseModel(object): + """ + Base Model for all classess + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """ + return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + + # also use __json__() if present to get additional fields + _json_attr = getattr(self, '__json__', None) + if _json_attr: + # update with attributes from __json__ + if callable(_json_attr): + _json_attr = _json_attr() + for k, val in _json_attr.iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session().query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def get_or_404(cls, id_): + try: + id_ = int(id_) + except (TypeError, ValueError): + raise HTTPNotFound + + res = cls.query().get(id_) + if not res: + raise HTTPNotFound + return res + + @classmethod + def getAll(cls): + # deprecated and left for backward compatibility + return cls.get_all() + + @classmethod + def get_all(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session().delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + try: + return safe_str(self.__unicode__()) + except UnicodeDecodeError: + pass + return '' % (self.__class__.__name__) + + +class RhodeCodeSetting(Base, BaseModel): + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + SETTINGS_TYPES = { + 'str': safe_str, + 'int': safe_int, + 'unicode': safe_unicode, + 'bool': str2bool, + 'list': functools.partial(aslist, sep=',') + } + DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions' + + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + _type = self.app_settings_type + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + return converter(v) + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + @hybrid_property + def app_settings_type(self): + return self._app_settings_type + + @app_settings_type.setter + def app_settings_type(self, val): + if val not in self.SETTINGS_TYPES: + raise Exception('type must be one of %s got %s' + % (self.SETTINGS_TYPES.keys(), val)) + self._app_settings_type = val + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value, self.app_settings_type + ) + + @classmethod + def get_by_name(cls, key): + return cls.query()\ + .filter(cls.app_settings_name == key).scalar() + + @classmethod + def get_by_name_or_create(cls, key, val='', type='unicode'): + res = cls.get_by_name(key) + if not res: + res = cls(key, val, type) + return res + + @classmethod + def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): + """ + Creates or updates RhodeCode setting. If updates is triggered it will only + update parameters that are explicityl set Optional instance will be skipped + + :param key: + :param val: + :param type: + :return: + """ + res = cls.get_by_name(key) + if not res: + val = Optional.extract(val) + type = Optional.extract(type) + res = cls(key, val, type) + else: + res.app_settings_name = key + if not isinstance(val, Optional): + # update if set + res.app_settings_value = val + if not isinstance(type, Optional): + # update if set + res.app_settings_type = type + return res + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + if cache: + ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = \ + each.app_settings_value + + return settings + + @classmethod + def get_auth_plugins(cls, cache=False): + auth_plugins = cls.get_by_name("auth_plugins").app_settings_value + return auth_plugins + + @classmethod + def get_auth_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('auth_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name: row.app_settings_value}) + + return fd + + @classmethod + def get_default_repo_settings(cls, cache=False, strip_prefix=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('default_')).all() + fd = {} + for row in ret: + key = row.app_settings_name + if strip_prefix: + key = remove_prefix(key, prefix='default_') + fd.update({key: row.app_settings_value}) + + return fd + + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': safe_unicode(platform.platform()), + 'rhodecode_version': rhodecode.__version__, + 'git_version': safe_unicode(check_git_version()), + 'git_path': rhodecode.CONFIG.get('git_path') + } + return info + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'changegroup.push_logger' + HOOK_PRE_PUSH = 'prechangegroup.pre_push' + HOOK_PULL = 'outgoing.pull_logger' + HOOK_PRE_PULL = 'preoutgoing.pre_pull' + + ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + # def __init__(self, section='', key='', value=''): + # self.ui_section = section + # self.ui_key = key + # self.ui_value = value + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key).scalar() + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def get_repos_location(cls): + return cls.get_by_key('/').ui_value + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key) or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session().add(new_ui) + + def __repr__(self): + return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section, + self.ui_key, self.ui_value) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + DEFAULT_USER = 'default' + DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}' + + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) + name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) + extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user_log = relationship('UserLog') + user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') + + repositories = relationship('Repository') + user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') + + repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UserGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + #extra emails for this user + user_emails = relationship('UserEmailMap', cascade='all') + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def firstname(self): + # alias for future + return self.name + + @property + def emails(self): + other = UserEmailMap.query().filter(UserEmailMap.user==self).all() + return [self.email] + [x.email for x in other] + + @property + def ip_addresses(self): + ret = UserIpMap.query().filter(UserIpMap.user == self).all() + return [x.ip_addr for x in ret] + + @property + def username_and_name(self): + return '%s (%s %s)' % (self.username, self.firstname, self.lastname) + + @property + def full_name(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.firstname, self.lastname) + if (self.firstname and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def is_admin(self): + return self.admin + + @property + def AuthUser(self): + """ + Returns instance of AuthUser for this user + """ + from rhodecode.lib.auth import AuthUser + return AuthUser(user_id=self.user_id, api_key=self.api_key, + username=self.username) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False, fallback=True): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + res = q.scalar() + + if fallback and not res: + #fallback to additional keys + _res = UserApiKeys.query()\ + .filter(UserApiKeys.api_key == api_key)\ + .filter(or_(UserApiKeys.expires == -1, + UserApiKeys.expires >= time.time()))\ + .first() + if _res: + res = _res.user + return res + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret + + @classmethod + def get_from_cs_author(cls, author): + """ + Tries to get User objects out of commit author string + + :param author: + """ + from rhodecode.lib.helpers import email, author_name + # Valid email in the attribute passed, see if they're in the system + _email = email(author) + if _email: + user = cls.get_by_email(_email, case_insensitive=True) + if user: + return user + # Maybe we can match by username? + _author = author_name(author) + user = cls.get_by_username(_author, case_insensitive=True) + if user: + return user + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session().add(self) + log.debug('updated user %s lastlogin' % self.username) + + @classmethod + def get_first_admin(cls): + user = User.query().filter(User.admin == True).first() + if user is None: + raise Exception('Missing administrative account!') + return user + + @classmethod + def get_default_user(cls, cache=False): + user = User.get_by_username(User.DEFAULT_USER, cache=cache) + if user is None: + raise Exception('Missing default account!') + return user + + def get_api_data(self): + """ + Common function for generating user related data for API + """ + user = self + data = dict( + user_id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + emails=user.emails, + api_key=user.api_key, + active=user.active, + admin=user.admin, + extern_type=user.extern_type, + extern_name=user.extern_name, + last_login=user.last_login, + ip_addresses=user.ip_addresses + ) + return data + + def __json__(self): + data = dict( + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + data.update(self.get_api_data()) + return data + + +class UserApiKeys(Base, BaseModel): + __tablename__ = 'user_api_keys' + __table_args__ = ( + Index('uak_api_key_idx', 'api_key'), + UniqueConstraint('api_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True) + description = Column('description', UnicodeText(1024)) + expires = Column('expires', Float(53), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user = relationship('User', lazy='joined') + + +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session().query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + +class UserIpMap(Base, BaseModel): + __tablename__ = 'user_ip_map' + __table_args__ = ( + UniqueConstraint('user_id', 'ip_addr'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + user = relationship('User', lazy='joined') + + @classmethod + def _get_ip_range(cls, ip_addr): + from rhodecode.lib import ipaddr + net = ipaddr.IPNetwork(address=ip_addr) + return [str(net.network), str(net.broadcast)] + + def __json__(self): + return dict( + ip_addr=self.ip_addr, + ip_range=self._get_ip_range(self.ip_addr) + ) + + def __unicode__(self): + return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__, + self.user_id, self.ip_addr) + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) + repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.repository_name, + self.action) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UserGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + users_group_to_perm = relationship('UserGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') + user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') + + user = relationship('User') + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.users_group_id, + self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, user_group_id, cache=False): + user_group = cls.query() + if cache: + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self + + data = dict( + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + group_description=user_group.user_group_description, + active=user_group.users_group_active, + owner=user_group.user.username, + ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members + + return data + + +class UserGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + + user = relationship('User', lazy='joined') + users_group = relationship('UserGroup') + + def __init__(self, gr_id='', u_id=''): + self.users_group_id = gr_id + self.user_id = u_id + + +class RepositoryField(Base, BaseModel): + __tablename__ = 'repositories_fields' + __table_args__ = ( + UniqueConstraint('repository_id', 'field_key'), # no-multi field + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields + + repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) + field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_type = Column("field_type", String(256), nullable=False, unique=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repository = relationship('Repository') + + @property + def field_key_prefixed(self): + return 'ex_%s' % self.field_key + + @classmethod + def un_prefix_key(cls, key): + if key.startswith(cls.PREFIX): + return key[len(cls.PREFIX):] + return key + + @classmethod + def get_by_key_name(cls, key, repo): + row = cls.query()\ + .filter(cls.repository == repo)\ + .filter(cls.field_key == key).scalar() + return row + + +class Repository(Base, BaseModel): + __tablename__ = 'repositories' + __table_args__ = ( + UniqueConstraint('repo_name'), + Index('r_repo_name_idx', 'repo_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}' + + repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + private = Column("private", Boolean(), nullable=True, unique=None, default=None) + enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) + enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) + description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data + + fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + stats = relationship('Statistics', cascade='all', uselist=False) + + followers = relationship('UserFollowing', + primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', + cascade='all') + extra_fields = relationship('RepositoryField', + cascade="all, delete, delete-orphan") + + logs = relationship('UserLog') + comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") + + pull_requests_org = relationship('PullRequest', + primaryjoin='PullRequest.org_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + pull_requests_other = relationship('PullRequest', + primaryjoin='PullRequest.other_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, + safe_unicode(self.repo_name)) + + @hybrid_property + def landing_rev(self): + # always should return [rev_type, rev] + if self._landing_revision: + _rev_info = self._landing_revision.split(':') + if len(_rev_info) < 2: + _rev_info.insert(0, 'rev') + return [_rev_info[0], _rev_info[1]] + return [None, None] + + @landing_rev.setter + def landing_rev(self, val): + if ':' not in val: + raise ValueError('value must be delimited with `:` and consist ' + 'of :, got %s instead' % val) + self._landing_revision = val + + @hybrid_property + def locked(self): + # always should return [user_id, timelocked] + if self._locked: + _lock_info = self._locked.split(':') + return int(_lock_info[0]), _lock_info[1] + return [None, None] + + @locked.setter + def locked(self, val): + if val and isinstance(val, (list, tuple)): + self._locked = ':'.join(map(str, val)) + else: + self._locked = None + + @hybrid_property + def changeset_cache(self): + from rhodecode.lib.vcs.backends.base import EmptyChangeset + dummy = EmptyChangeset().__json__() + if not self._changeset_cache: + return dummy + try: + return json.loads(self._changeset_cache) + except TypeError: + return dummy + + @changeset_cache.setter + def changeset_cache(self, val): + try: + self._changeset_cache = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def normalize_repo_name(cls, repo_name): + """ + Normalizes os specific repo_name to the format internally stored inside + dabatabase using URL_SEP + + :param cls: + :param repo_name: + """ + return cls.url_sep().join(repo_name.split(os.sep)) + + @classmethod + def get_by_repo_name(cls, repo_name): + q = Session().query(cls).filter(cls.repo_name == repo_name) + q = q.options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group)) + return q.scalar() + + @classmethod + def get_by_full_path(cls, repo_full_path): + repo_name = repo_full_path.split(cls.base_path(), 1)[-1] + repo_name = cls.normalize_repo_name(repo_name) + return cls.get_by_repo_name(repo_name.strip(URL_SEP)) + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + def groups_with_parents(self): + groups = [] + if self.group is None: + return groups + + cur_gr = self.group + groups.insert(0, cur_gr) + while 1: + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + groups.insert(0, gr) + + return groups + + @property + def groups_and_repo(self): + return self.groups_with_parents, self.just_name, self.repo_name + + @LazyProperty + def repo_path(self): + """ + Returns base full path for that repository means where it actually + exists on a filesystem + """ + q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def repo_full_path(self): + p = [self.repo_path] + # we need to split the name by / since this is how we store the + # names in the database, but that eventually needs to be converted + # into a valid system path + p += self.repo_name.split(Repository.url_sep()) + return os.path.join(*map(safe_unicode, p)) + + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + def _ui(self): + """ + Creates an db based ui object for this repository + """ + from rhodecode.lib.utils import make_ui + return make_ui('db', clear_session=False) + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + def get_api_data(self): + """ + Common function for generating repo api data + + """ + repo = self + data = dict( + repo_id=repo.repo_id, + repo_name=repo.repo_name, + repo_type=repo.repo_type, + clone_uri=repo.clone_uri, + private=repo.private, + created_on=repo.created_on, + description=repo.description, + landing_rev=repo.landing_rev, + owner=repo.user.username, + fork_of=repo.fork.repo_name if repo.fork else None, + enable_statistics=repo.enable_statistics, + enable_locking=repo.enable_locking, + enable_downloads=repo.enable_downloads, + last_changeset=repo.changeset_cache, + locked_by=User.get(self.locked[0]).get_api_data() \ + if self.locked[0] else None, + locked_date=time_to_datetime(self.locked[1]) \ + if self.locked[1] else None + ) + rc_config = RhodeCodeSetting.get_app_settings() + repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) + if repository_fields: + for f in self.extra_fields: + data[f.field_key_prefixed] = f.field_value + + return data + + @classmethod + def lock(cls, repo, user_id, lock_time=None): + if not lock_time: + lock_time = time.time() + repo.locked = [user_id, lock_time] + Session().add(repo) + Session().commit() + + @classmethod + def unlock(cls, repo): + repo.locked = None + Session().add(repo) + Session().commit() + + @classmethod + def getlock(cls, repo): + return repo.locked + + @property + def last_db_change(self): + return self.updated_on + + def clone_url(self, **override): + from pylons import url + from urlparse import urlparse + import urllib + parsed_url = urlparse(url('home', qualified=True)) + default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' + decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) + args = { + 'user': '', + 'pass': '', + 'scheme': parsed_url.scheme, + 'netloc': parsed_url.netloc, + 'prefix': decoded_path, + 'path': self.repo_name + } + + args.update(override) + return default_clone_uri % args + + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev=None): + return get_changeset_safe(self.scm_instance, rev) + + def get_landing_changeset(self): + """ + Returns landing changeset, or if that doesn't exist returns the tip + """ + _rev_type, _rev = self.landing_rev + cs = self.get_changeset(_rev) + if isinstance(cs, EmptyChangeset): + return self.get_changeset() + return cs + + def update_changeset_cache(self, cs_cache=None): + """ + Update cache of last changeset for repository, keys should be:: + + short_id + raw_id + revision + message + date + author + + :param cs_cache: + """ + from rhodecode.lib.vcs.backends.base import BaseChangeset + if cs_cache is None: + cs_cache = EmptyChangeset() + # use no-cache version here + scm_repo = self.scm_instance_no_cache() + if scm_repo: + cs_cache = scm_repo.get_changeset() + + if isinstance(cs_cache, BaseChangeset): + cs_cache = cs_cache.__json__() + + if (cs_cache != self.changeset_cache or not self.changeset_cache): + _default = datetime.datetime.fromtimestamp(0) + last_change = cs_cache.get('date') or _default + log.debug('updated repo %s with new cs cache %s' + % (self.repo_name, cs_cache)) + self.updated_on = last_change + self.changeset_cache = cs_cache + Session().add(self) + Session().commit() + else: + log.debug('Skipping repo:%s already with latest changes' + % self.repo_name) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author + + @property + def last_change(self): + return self.scm_instance.last_change + + def get_comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = collections.defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + + #maybe we have open new pullrequest without a status ? + stat = ChangesetStatus.STATUS_UNDER_REVIEW + status_lbl = ChangesetStatus.get_status_lbl(stat) + for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): + for rev in pr.revisions: + pr_id = pr.pull_request_id + pr_repo = pr.other_repo.repo_name + grouped[rev] = [stat, status_lbl, pr_id, pr_repo] + + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + + def _repo_size(self): + from rhodecode.lib import helpers as h + log.debug('calculating repository size...') + return h.format_byte_size(self.scm_instance.size) + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + def set_invalidate(self): + """ + Mark caches of this repo as invalid. + """ + CacheInvalidation.set_invalidate(self.repo_name) + + def scm_instance_no_cache(self): + return self.__get_instance() + + @property + def scm_instance(self): + import rhodecode + full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) + if full_cache: + return self.scm_instance_cached() + return self.__get_instance() + + def scm_instance_cached(self, valid_cache_keys=None): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + + valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) + if not valid: + log.debug('Cache for %s invalidated, getting new object' % (rn)) + region_invalidate(_c, None, rn) + else: + log.debug('Getting obj for %s from cache' % (rn)) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository from %s' + % (alias, repo_full_path)) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + log.error('Perhaps this repository is in db and not in ' + 'filesystem run rescan repositories with ' + '"destroy old data " option from admin panel') + return + + if alias == 'hg': + + repo = backend(safe_str(repo_full_path), create=False, + baseui=self._ui) + # skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + def __json__(self): + return dict(landing_rev = self.landing_rev) + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + __mapper_args__ = {'order_by': 'group_name'} + + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) + user = relationship('User') + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def groups_choices(cls, groups=None, show_empty_group=True): + from webhelpers.html import literal as _literal + if not groups: + groups = cls.query().all() + + repo_groups = [] + if show_empty_group: + repo_groups = [('-1', u'-- %s --' % _('top level'))] + sep = ' » ' + _name = lambda k: _literal(sep.join(k)) + + repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) + for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def _recursive_objects(self, include_repos=True): + all_ = [] + + def _get_members(root_gr): + if include_repos: + for r in root_gr.repositories: + all_.append(r) + childs = root_gr.children.all() + if childs: + for gr in childs: + all_.append(gr) + _get_members(gr) + + _get_members(self) + return [self] + all_ + + def recursive_groups_and_repos(self): + """ + Recursive return all groups, with repositories in those groups + """ + return self._recursive_objects() + + def recursive_groups(self): + """ + Returns all children groups for this group including children of children + """ + return self._recursive_objects(include_repos=False) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PERMS = [ + ('hg.admin', _('RhodeCode Administrator')), + + ('repository.none', _('Repository no access')), + ('repository.read', _('Repository read access')), + ('repository.write', _('Repository write access')), + ('repository.admin', _('Repository admin access')), + + ('group.none', _('Repository group no access')), + ('group.read', _('Repository group read access')), + ('group.write', _('Repository group write access')), + ('group.admin', _('Repository group admin access')), + + ('usergroup.none', _('User group no access')), + ('usergroup.read', _('User group read access')), + ('usergroup.write', _('User group write access')), + ('usergroup.admin', _('User group admin access')), + + ('hg.repogroup.create.false', _('Repository Group creation disabled')), + ('hg.repogroup.create.true', _('Repository Group creation enabled')), + + ('hg.usergroup.create.false', _('User Group creation disabled')), + ('hg.usergroup.create.true', _('User Group creation enabled')), + + ('hg.create.none', _('Repository creation disabled')), + ('hg.create.repository', _('Repository creation enabled')), + + ('hg.fork.none', _('Repository forking disabled')), + ('hg.fork.repository', _('Repository forking enabled')), + + ('hg.register.none', _('Registration disabled')), + ('hg.register.manual_activate', _('User Registration with manual account activation')), + ('hg.register.auto_activate', _('User Registration with automatic account activation')), + + ('hg.extern_activate.manual', _('Manual activation of external account')), + ('hg.extern_activate.auto', _('Automatic activation of external account')), + + ] + + #definition of system default permissions for DEFAULT user + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'usergroup.read', + 'hg.create.repository', + 'hg.fork.repository', + 'hg.register.manual_activate', + 'hg.extern_activate.auto', + ] + + # defines which permissions are more important higher the more important + # Weight defines which permissions are more important. + # The higher number the more important. + PERM_WEIGHTS = { + 'repository.none': 0, + 'repository.read': 1, + 'repository.write': 3, + 'repository.admin': 4, + + 'group.none': 0, + 'group.read': 1, + 'group.write': 3, + 'group.admin': 4, + + 'usergroup.none': 0, + 'usergroup.read': 1, + 'usergroup.write': 3, + 'usergroup.admin': 4, + 'hg.repogroup.create.false': 0, + 'hg.repogroup.create.true': 1, + + 'hg.usergroup.create.false': 0, + 'hg.usergroup.create.true': 1, + + 'hg.fork.none': 0, + 'hg.fork.repository': 1, + 'hg.create.none': 0, + 'hg.create.repository': 1 + } + + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session().query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_user_group_perms(cls, default_user_id): + q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ + .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ + .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserUserGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.repository) + + +class UserUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'user_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + user_group = relationship('UserGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, user_group, permission): + n = cls() + n.user = user + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.user_group) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.permission) + + +class UserGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) + + +class UserGroupUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_group_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), + CheckConstraint('target_user_group_id != user_group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') + user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') + permission = relationship('Permission') + + @classmethod + def create(cls, target_user_group, user_group, permission): + n = cls() + n.target_user_group = target_user_group + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.target_user_group, self.user_group) + + +class UserGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + +class UserGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + Index('key_idx', 'cache_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + # cache_id, not used + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + # cache_key as created by _get_cache_key + cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # cache_args is a repo_name + cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + # instance sets cache_active True when it is caching, + # other instances set cache_active to False to indicate that this cache is invalid + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, repo_name=''): + self.cache_key = cache_key + self.cache_args = repo_name + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, + self.cache_id, self.cache_key, self.cache_active) + + def _cache_key_partition(self): + prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) + return prefix, repo_name, suffix + + def get_prefix(self): + """ + get prefix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[0] + + def get_suffix(self): + """ + get suffix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[2] + + @classmethod + def clear_cache(cls): + """ + Delete all cache keys from database. + Should only be run when all instances are down and all entries thus stale. + """ + cls.query().delete() + Session().commit() + + @classmethod + def _get_cache_key(cls, key): + """ + Wrapper for generating a unique cache key for this instance and "key". + key must / will start with a repo_name which will be stored in .cache_args . + """ + import rhodecode + prefix = rhodecode.CONFIG.get('instance_id', '') + return "%s%s" % (prefix, key) + + @classmethod + def set_invalidate(cls, repo_name, delete=False): + """ + Mark all caches of a repo as invalid in the database. + """ + inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() + + try: + for inv_obj in inv_objs: + log.debug('marking %s key for invalidation based on repo_name=%s' + % (inv_obj, safe_str(repo_name))) + if delete: + Session().delete(inv_obj) + else: + inv_obj.cache_active = False + Session().add(inv_obj) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + + @classmethod + def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): + """ + Mark this cache key as active and currently cached. + Return True if the existing cache registration still was valid. + Return False to indicate that it had been invalidated and caches should be refreshed. + """ + + key = (repo_name + '_' + kind) if kind else repo_name + cache_key = cls._get_cache_key(key) + + if valid_cache_keys and cache_key in valid_cache_keys: + return True + + try: + inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() + if not inv_obj: + inv_obj = CacheInvalidation(cache_key, repo_name) + was_valid = inv_obj.cache_active + inv_obj.cache_active = True + Session().add(inv_obj) + Session().commit() + return was_valid + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + return False + + @classmethod + def get_valid_cache_keys(cls): + """ + Return opaque object with information of which caches still are valid + and can be used without checking for invalidation. + """ + return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + Index('cc_revision_idx', 'revision'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + line_no = Column('line_no', Unicode(10), nullable=True) + hl_lines = Column('hl_lines', Unicode(512), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', UnicodeText(25000), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_users(cls, revision=None, pull_request_id=None): + """ + Returns user associated with this ChangesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + q = Session().query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' + + STATUSES = [ + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), + ] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + # values for .status + STATUS_NEW = u'new' + STATUS_OPEN = u'open' + STATUS_CLOSED = u'closed' + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', UnicodeText(10240), nullable=True) + status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + @property + def org_ref_parts(self): + return self.org_ref.split(':') + + @property + def other_ref_parts(self): + return self.other_ref.split(':') + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers', + cascade="all, delete, delete-orphan") + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + statuses = relationship('ChangesetStatus') + comments = relationship('ChangesetComment', + cascade="all, delete, delete-orphan") + + def is_closed(self): + return self.status == self.STATUS_CLOSED + + @property + def last_review_status(self): + return self.statuses[-1].status if self.statuses else '' + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', UnicodeText(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self)\ + .order_by(UserNotification.user_id.asc()).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session().add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session().add(self) + + +class Gist(Base, BaseModel): + __tablename__ = 'gists' + __table_args__ = ( + Index('g_gist_access_id_idx', 'gist_access_id'), + Index('g_created_on_idx', 'created_on'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + GIST_PUBLIC = u'public' + GIST_PRIVATE = u'private' + + gist_id = Column('gist_id', Integer(), primary_key=True) + gist_access_id = Column('gist_access_id', Unicode(250)) + gist_description = Column('gist_description', UnicodeText(1024)) + gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) + gist_expires = Column('gist_expires', Float(53), nullable=False) + gist_type = Column('gist_type', Unicode(128), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + owner = relationship('User') + + @classmethod + def get_or_404(cls, id_): + res = cls.query().filter(cls.gist_access_id == id_).scalar() + if not res: + raise HTTPNotFound + return res + + @classmethod + def get_by_access_id(cls, gist_access_id): + return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() + + def gist_url(self): + import rhodecode + alias_url = rhodecode.CONFIG.get('gist_alias_url') + if alias_url: + return alias_url.replace('{gistid}', self.gist_access_id) + + from pylons import url + return url('gist', gist_id=self.gist_access_id, qualified=True) + + @classmethod + def base_path(cls): + """ + Returns base path when all gists are stored + + :param cls: + """ + from rhodecode.model.gist import GIST_STORE_LOC + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == URL_SEP) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return os.path.join(q.one().ui_value, GIST_STORE_LOC) + + def get_api_data(self): + """ + Common function for generating gist related data for API + """ + gist = self + data = dict( + gist_id=gist.gist_id, + type=gist.gist_type, + access_id=gist.gist_access_id, + description=gist.gist_description, + url=gist.gist_url(), + expires=gist.gist_expires, + created_on=gist.created_on, + ) + return data + + def __json__(self): + data = dict( + ) + data.update(self.get_api_data()) + return data + ## SCM functions + + @property + def scm_instance(self): + from rhodecode.lib.vcs import get_repo + base_path = self.base_path() + return get_repo(os.path.join(*map(safe_str, + [base_path, self.gist_access_id]))) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_2_2_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_2_2_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,2448 @@ +# -*- 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 . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import time +import logging +import datetime +import traceback +import hashlib +import collections +import functools + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError +from beaker.cache import cache_region, region_invalidate +from webob.exc import HTTPNotFound + +from pylons.i18n.translation import lazy_ugettext as _ + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.lib.vcs.backends.base import EmptyChangeset + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \ + get_clone_url +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session + +URL_SEP = '/' +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +class BaseModel(object): + """ + Base Model for all classess + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """ + return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + + # also use __json__() if present to get additional fields + _json_attr = getattr(self, '__json__', None) + if _json_attr: + # update with attributes from __json__ + if callable(_json_attr): + _json_attr = _json_attr() + for k, val in _json_attr.iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session().query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def get_or_404(cls, id_): + try: + id_ = int(id_) + except (TypeError, ValueError): + raise HTTPNotFound + + res = cls.query().get(id_) + if not res: + raise HTTPNotFound + return res + + @classmethod + def getAll(cls): + # deprecated and left for backward compatibility + return cls.get_all() + + @classmethod + def get_all(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session().delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + try: + return safe_str(self.__unicode__()) + except UnicodeDecodeError: + pass + return '' % (self.__class__.__name__) + + +class RhodeCodeSetting(Base, BaseModel): + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + SETTINGS_TYPES = { + 'str': safe_str, + 'int': safe_int, + 'unicode': safe_unicode, + 'bool': str2bool, + 'list': functools.partial(aslist, sep=',') + } + DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions' + + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + _type = self.app_settings_type + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + return converter(v) + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + @hybrid_property + def app_settings_type(self): + return self._app_settings_type + + @app_settings_type.setter + def app_settings_type(self, val): + if val not in self.SETTINGS_TYPES: + raise Exception('type must be one of %s got %s' + % (self.SETTINGS_TYPES.keys(), val)) + self._app_settings_type = val + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value, self.app_settings_type + ) + + @classmethod + def get_by_name(cls, key): + return cls.query()\ + .filter(cls.app_settings_name == key).scalar() + + @classmethod + def get_by_name_or_create(cls, key, val='', type='unicode'): + res = cls.get_by_name(key) + if not res: + res = cls(key, val, type) + return res + + @classmethod + def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): + """ + Creates or updates RhodeCode setting. If updates is triggered it will only + update parameters that are explicityl set Optional instance will be skipped + + :param key: + :param val: + :param type: + :return: + """ + res = cls.get_by_name(key) + if not res: + val = Optional.extract(val) + type = Optional.extract(type) + res = cls(key, val, type) + else: + res.app_settings_name = key + if not isinstance(val, Optional): + # update if set + res.app_settings_value = val + if not isinstance(type, Optional): + # update if set + res.app_settings_type = type + return res + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + if cache: + ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = \ + each.app_settings_value + + return settings + + @classmethod + def get_auth_plugins(cls, cache=False): + auth_plugins = cls.get_by_name("auth_plugins").app_settings_value + return auth_plugins + + @classmethod + def get_auth_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('auth_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name: row.app_settings_value}) + + return fd + + @classmethod + def get_default_repo_settings(cls, cache=False, strip_prefix=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('default_')).all() + fd = {} + for row in ret: + key = row.app_settings_name + if strip_prefix: + key = remove_prefix(key, prefix='default_') + fd.update({key: row.app_settings_value}) + + return fd + + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': safe_unicode(platform.platform()), + 'rhodecode_version': rhodecode.__version__, + 'git_version': safe_unicode(check_git_version()), + 'git_path': rhodecode.CONFIG.get('git_path') + } + return info + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'changegroup.push_logger' + HOOK_PRE_PUSH = 'prechangegroup.pre_push' + HOOK_PULL = 'outgoing.pull_logger' + HOOK_PRE_PULL = 'preoutgoing.pre_pull' + + ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + # def __init__(self, section='', key='', value=''): + # self.ui_section = section + # self.ui_key = key + # self.ui_value = value + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key).scalar() + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def get_repos_location(cls): + return cls.get_by_key('/').ui_value + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key) or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session().add(new_ui) + + def __repr__(self): + return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section, + self.ui_key, self.ui_value) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + DEFAULT_USER = 'default' + DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}' + + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) + name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) + extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + #_user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data + + user_log = relationship('UserLog') + user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') + + repositories = relationship('Repository') + user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') + + repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UserGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + #extra emails for this user + user_emails = relationship('UserEmailMap', cascade='all') + #extra api keys + user_api_keys = relationship('UserApiKeys', cascade='all') + + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def firstname(self): + # alias for future + return self.name + + @property + def emails(self): + other = UserEmailMap.query().filter(UserEmailMap.user==self).all() + return [self.email] + [x.email for x in other] + + @property + def api_keys(self): + other = UserApiKeys.query().filter(UserApiKeys.user==self).all() + return [self.api_key] + [x.api_key for x in other] + + @property + def ip_addresses(self): + ret = UserIpMap.query().filter(UserIpMap.user == self).all() + return [x.ip_addr for x in ret] + + @property + def username_and_name(self): + return '%s (%s %s)' % (self.username, self.firstname, self.lastname) + + @property + def full_name(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.firstname, self.lastname) + if (self.firstname and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def is_admin(self): + return self.admin + + @property + def AuthUser(self): + """ + Returns instance of AuthUser for this user + """ + from rhodecode.lib.auth import AuthUser + return AuthUser(user_id=self.user_id, api_key=self.api_key, + username=self.username) + + @hybrid_property + def user_data(self): + if not self._user_data: + return {} + + try: + return json.loads(self._user_data) + except TypeError: + return {} + + @user_data.setter + def user_data(self, val): + try: + self._user_data = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False, fallback=True): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + res = q.scalar() + + if fallback and not res: + #fallback to additional keys + _res = UserApiKeys.query()\ + .filter(UserApiKeys.api_key == api_key)\ + .filter(or_(UserApiKeys.expires == -1, + UserApiKeys.expires >= time.time()))\ + .first() + if _res: + res = _res.user + return res + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret + + @classmethod + def get_from_cs_author(cls, author): + """ + Tries to get User objects out of commit author string + + :param author: + """ + from rhodecode.lib.helpers import email, author_name + # Valid email in the attribute passed, see if they're in the system + _email = email(author) + if _email: + user = cls.get_by_email(_email, case_insensitive=True) + if user: + return user + # Maybe we can match by username? + _author = author_name(author) + user = cls.get_by_username(_author, case_insensitive=True) + if user: + return user + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session().add(self) + log.debug('updated user %s lastlogin' % self.username) + + @classmethod + def get_first_admin(cls): + user = User.query().filter(User.admin == True).first() + if user is None: + raise Exception('Missing administrative account!') + return user + + @classmethod + def get_default_user(cls, cache=False): + user = User.get_by_username(User.DEFAULT_USER, cache=cache) + if user is None: + raise Exception('Missing default account!') + return user + + def get_api_data(self): + """ + Common function for generating user related data for API + """ + user = self + data = dict( + user_id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + emails=user.emails, + api_key=user.api_key, + api_keys=user.api_keys, + active=user.active, + admin=user.admin, + extern_type=user.extern_type, + extern_name=user.extern_name, + last_login=user.last_login, + ip_addresses=user.ip_addresses + ) + return data + + def __json__(self): + data = dict( + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + data.update(self.get_api_data()) + return data + + +class UserApiKeys(Base, BaseModel): + __tablename__ = 'user_api_keys' + __table_args__ = ( + Index('uak_api_key_idx', 'api_key'), + Index('uak_api_key_expires_idx', 'api_key', 'expires'), + UniqueConstraint('api_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True) + description = Column('description', UnicodeText(1024)) + expires = Column('expires', Float(53), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user = relationship('User', lazy='joined') + + @property + def expired(self): + if self.expires == -1: + return False + return time.time() > self.expires + + +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session().query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + +class UserIpMap(Base, BaseModel): + __tablename__ = 'user_ip_map' + __table_args__ = ( + UniqueConstraint('user_id', 'ip_addr'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + user = relationship('User', lazy='joined') + + @classmethod + def _get_ip_range(cls, ip_addr): + from rhodecode.lib import ipaddr + net = ipaddr.IPNetwork(address=ip_addr) + return [str(net.network), str(net.broadcast)] + + def __json__(self): + return dict( + ip_addr=self.ip_addr, + ip_range=self._get_ip_range(self.ip_addr) + ) + + def __unicode__(self): + return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__, + self.user_id, self.ip_addr) + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) + repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.repository_name, + self.action) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UserGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) + users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + users_group_to_perm = relationship('UserGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') + user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') + + user = relationship('User') + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.users_group_id, + self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, user_group_id, cache=False): + user_group = cls.query() + if cache: + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self + + data = dict( + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + group_description=user_group.user_group_description, + active=user_group.users_group_active, + owner=user_group.user.username, + ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members + + return data + + +class UserGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + + user = relationship('User', lazy='joined') + users_group = relationship('UserGroup') + + def __init__(self, gr_id='', u_id=''): + self.users_group_id = gr_id + self.user_id = u_id + + +class RepositoryField(Base, BaseModel): + __tablename__ = 'repositories_fields' + __table_args__ = ( + UniqueConstraint('repository_id', 'field_key'), # no-multi field + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields + + repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + field_key = Column("field_key", String(250, convert_unicode=False)) + field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False) + field_type = Column("field_type", String(256), nullable=False, unique=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repository = relationship('Repository') + + @property + def field_key_prefixed(self): + return 'ex_%s' % self.field_key + + @classmethod + def un_prefix_key(cls, key): + if key.startswith(cls.PREFIX): + return key[len(cls.PREFIX):] + return key + + @classmethod + def get_by_key_name(cls, key, repo): + row = cls.query()\ + .filter(cls.repository == repo)\ + .filter(cls.field_key == key).scalar() + return row + + +class Repository(Base, BaseModel): + __tablename__ = 'repositories' + __table_args__ = ( + UniqueConstraint('repo_name'), + Index('r_repo_name_idx', 'repo_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}' + DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}' + + STATE_CREATED = 'repo_state_created' + STATE_PENDING = 'repo_state_pending' + STATE_ERROR = 'repo_state_error' + + repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + private = Column("private", Boolean(), nullable=True, unique=None, default=None) + enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) + enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) + description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data + + fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + stats = relationship('Statistics', cascade='all', uselist=False) + + followers = relationship('UserFollowing', + primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', + cascade='all') + extra_fields = relationship('RepositoryField', + cascade="all, delete, delete-orphan") + + logs = relationship('UserLog') + comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") + + pull_requests_org = relationship('PullRequest', + primaryjoin='PullRequest.org_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + pull_requests_other = relationship('PullRequest', + primaryjoin='PullRequest.other_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, + safe_unicode(self.repo_name)) + + @hybrid_property + def landing_rev(self): + # always should return [rev_type, rev] + if self._landing_revision: + _rev_info = self._landing_revision.split(':') + if len(_rev_info) < 2: + _rev_info.insert(0, 'rev') + return [_rev_info[0], _rev_info[1]] + return [None, None] + + @landing_rev.setter + def landing_rev(self, val): + if ':' not in val: + raise ValueError('value must be delimited with `:` and consist ' + 'of :, got %s instead' % val) + self._landing_revision = val + + @hybrid_property + def locked(self): + # always should return [user_id, timelocked] + if self._locked: + _lock_info = self._locked.split(':') + return int(_lock_info[0]), _lock_info[1] + return [None, None] + + @locked.setter + def locked(self, val): + if val and isinstance(val, (list, tuple)): + self._locked = ':'.join(map(str, val)) + else: + self._locked = None + + @hybrid_property + def changeset_cache(self): + from rhodecode.lib.vcs.backends.base import EmptyChangeset + dummy = EmptyChangeset().__json__() + if not self._changeset_cache: + return dummy + try: + return json.loads(self._changeset_cache) + except TypeError: + return dummy + + @changeset_cache.setter + def changeset_cache(self, val): + try: + self._changeset_cache = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def normalize_repo_name(cls, repo_name): + """ + Normalizes os specific repo_name to the format internally stored inside + dabatabase using URL_SEP + + :param cls: + :param repo_name: + """ + return cls.url_sep().join(repo_name.split(os.sep)) + + @classmethod + def get_by_repo_name(cls, repo_name): + q = Session().query(cls).filter(cls.repo_name == repo_name) + q = q.options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group)) + return q.scalar() + + @classmethod + def get_by_full_path(cls, repo_full_path): + repo_name = repo_full_path.split(cls.base_path(), 1)[-1] + repo_name = cls.normalize_repo_name(repo_name) + return cls.get_by_repo_name(repo_name.strip(URL_SEP)) + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + def groups_with_parents(self): + groups = [] + if self.group is None: + return groups + + cur_gr = self.group + groups.insert(0, cur_gr) + while 1: + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + groups.insert(0, gr) + + return groups + + @property + def groups_and_repo(self): + return self.groups_with_parents, self.just_name, self.repo_name + + @LazyProperty + def repo_path(self): + """ + Returns base full path for that repository means where it actually + exists on a filesystem + """ + q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def repo_full_path(self): + p = [self.repo_path] + # we need to split the name by / since this is how we store the + # names in the database, but that eventually needs to be converted + # into a valid system path + p += self.repo_name.split(Repository.url_sep()) + return os.path.join(*map(safe_unicode, p)) + + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + def _ui(self): + """ + Creates an db based ui object for this repository + """ + from rhodecode.lib.utils import make_ui + return make_ui('db', clear_session=False) + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + def get_api_data(self): + """ + Common function for generating repo api data + + """ + repo = self + data = dict( + repo_id=repo.repo_id, + repo_name=repo.repo_name, + repo_type=repo.repo_type, + clone_uri=repo.clone_uri, + private=repo.private, + created_on=repo.created_on, + description=repo.description, + landing_rev=repo.landing_rev, + owner=repo.user.username, + fork_of=repo.fork.repo_name if repo.fork else None, + enable_statistics=repo.enable_statistics, + enable_locking=repo.enable_locking, + enable_downloads=repo.enable_downloads, + last_changeset=repo.changeset_cache, + locked_by=User.get(self.locked[0]).get_api_data() \ + if self.locked[0] else None, + locked_date=time_to_datetime(self.locked[1]) \ + if self.locked[1] else None + ) + rc_config = RhodeCodeSetting.get_app_settings() + repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) + if repository_fields: + for f in self.extra_fields: + data[f.field_key_prefixed] = f.field_value + + return data + + @classmethod + def lock(cls, repo, user_id, lock_time=None): + if not lock_time: + lock_time = time.time() + repo.locked = [user_id, lock_time] + Session().add(repo) + Session().commit() + + @classmethod + def unlock(cls, repo): + repo.locked = None + Session().add(repo) + Session().commit() + + @classmethod + def getlock(cls, repo): + return repo.locked + + @property + def last_db_change(self): + return self.updated_on + + def clone_url(self, **override): + from pylons import url + qualified_home_url = url('home', qualified=True) + + uri_tmpl = None + if 'uri_tmpl' in override: + uri_tmpl = override['uri_tmpl'] + del override['uri_tmpl'] + + # we didn't override our tmpl from **overrides + if not uri_tmpl: + uri_tmpl = self.DEFAULT_CLONE_URI + try: + from pylons import tmpl_context as c + uri_tmpl = c.clone_uri_tmpl + except Exception: + # in any case if we call this outside of request context, + # ie, not having tmpl_context set up + pass + + return get_clone_url(uri_tmpl=uri_tmpl, + qualifed_home_url=qualified_home_url, + repo_name=self.repo_name, + repo_id=self.repo_id, **override) + + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev=None): + return get_changeset_safe(self.scm_instance, rev) + + def get_landing_changeset(self): + """ + Returns landing changeset, or if that doesn't exist returns the tip + """ + _rev_type, _rev = self.landing_rev + cs = self.get_changeset(_rev) + if isinstance(cs, EmptyChangeset): + return self.get_changeset() + return cs + + def update_changeset_cache(self, cs_cache=None): + """ + Update cache of last changeset for repository, keys should be:: + + short_id + raw_id + revision + message + date + author + + :param cs_cache: + """ + from rhodecode.lib.vcs.backends.base import BaseChangeset + if cs_cache is None: + cs_cache = EmptyChangeset() + # use no-cache version here + scm_repo = self.scm_instance_no_cache() + if scm_repo: + cs_cache = scm_repo.get_changeset() + + if isinstance(cs_cache, BaseChangeset): + cs_cache = cs_cache.__json__() + + if (cs_cache != self.changeset_cache or not self.changeset_cache): + _default = datetime.datetime.fromtimestamp(0) + last_change = cs_cache.get('date') or _default + log.debug('updated repo %s with new cs cache %s' + % (self.repo_name, cs_cache)) + self.updated_on = last_change + self.changeset_cache = cs_cache + Session().add(self) + Session().commit() + else: + log.debug('Skipping repo:%s already with latest changes' + % self.repo_name) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author + + @property + def last_change(self): + return self.scm_instance.last_change + + def get_comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = collections.defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + + #maybe we have open new pullrequest without a status ? + stat = ChangesetStatus.STATUS_UNDER_REVIEW + status_lbl = ChangesetStatus.get_status_lbl(stat) + for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): + for rev in pr.revisions: + pr_id = pr.pull_request_id + pr_repo = pr.other_repo.repo_name + grouped[rev] = [stat, status_lbl, pr_id, pr_repo] + + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + + def _repo_size(self): + from rhodecode.lib import helpers as h + log.debug('calculating repository size...') + return h.format_byte_size(self.scm_instance.size) + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + def set_invalidate(self): + """ + Mark caches of this repo as invalid. + """ + CacheInvalidation.set_invalidate(self.repo_name) + + def scm_instance_no_cache(self): + return self.__get_instance() + + @property + def scm_instance(self): + import rhodecode + full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) + if full_cache: + return self.scm_instance_cached() + return self.__get_instance() + + def scm_instance_cached(self, valid_cache_keys=None): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + + valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) + if not valid: + log.debug('Cache for %s invalidated, getting new object' % (rn)) + region_invalidate(_c, None, rn) + else: + log.debug('Getting obj for %s from cache' % (rn)) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository from %s' + % (alias, repo_full_path)) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + log.error('Perhaps this repository is in db and not in ' + 'filesystem run rescan repositories with ' + '"destroy old data " option from admin panel') + return + + if alias == 'hg': + + repo = backend(safe_str(repo_full_path), create=False, + baseui=self._ui) + # skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + def __json__(self): + return dict(landing_rev = self.landing_rev) + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + __mapper_args__ = {'order_by': 'group_name'} + + SEP = ' » ' + + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) + user = relationship('User') + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def _generate_choice(cls, repo_group): + from webhelpers.html import literal as _literal + _name = lambda k: _literal(cls.SEP.join(k)) + return repo_group.group_id, _name(repo_group.full_path_splitted) + + @classmethod + def groups_choices(cls, groups=None, show_empty_group=True): + if not groups: + groups = cls.query().all() + + repo_groups = [] + if show_empty_group: + repo_groups = [('-1', u'-- %s --' % _('top level'))] + + repo_groups.extend([cls._generate_choice(x) for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def _recursive_objects(self, include_repos=True): + all_ = [] + + def _get_members(root_gr): + if include_repos: + for r in root_gr.repositories: + all_.append(r) + childs = root_gr.children.all() + if childs: + for gr in childs: + all_.append(gr) + _get_members(gr) + + _get_members(self) + return [self] + all_ + + def recursive_groups_and_repos(self): + """ + Recursive return all groups, with repositories in those groups + """ + return self._recursive_objects() + + def recursive_groups(self): + """ + Returns all children groups for this group including children of children + """ + return self._recursive_objects(include_repos=False) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PERMS = [ + ('hg.admin', _('RhodeCode Administrator')), + + ('repository.none', _('Repository no access')), + ('repository.read', _('Repository read access')), + ('repository.write', _('Repository write access')), + ('repository.admin', _('Repository admin access')), + + ('group.none', _('Repository group no access')), + ('group.read', _('Repository group read access')), + ('group.write', _('Repository group write access')), + ('group.admin', _('Repository group admin access')), + + ('usergroup.none', _('User group no access')), + ('usergroup.read', _('User group read access')), + ('usergroup.write', _('User group write access')), + ('usergroup.admin', _('User group admin access')), + + ('hg.repogroup.create.false', _('Repository Group creation disabled')), + ('hg.repogroup.create.true', _('Repository Group creation enabled')), + + ('hg.usergroup.create.false', _('User Group creation disabled')), + ('hg.usergroup.create.true', _('User Group creation enabled')), + + ('hg.create.none', _('Repository creation disabled')), + ('hg.create.repository', _('Repository creation enabled')), + ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')), + ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')), + + ('hg.fork.none', _('Repository forking disabled')), + ('hg.fork.repository', _('Repository forking enabled')), + + ('hg.register.none', _('Registration disabled')), + ('hg.register.manual_activate', _('User Registration with manual account activation')), + ('hg.register.auto_activate', _('User Registration with automatic account activation')), + + ('hg.extern_activate.manual', _('Manual activation of external account')), + ('hg.extern_activate.auto', _('Automatic activation of external account')), + + ] + + #definition of system default permissions for DEFAULT user + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'usergroup.read', + 'hg.create.repository', + 'hg.create.write_on_repogroup.true', + 'hg.fork.repository', + 'hg.register.manual_activate', + 'hg.extern_activate.auto', + ] + + # defines which permissions are more important higher the more important + # Weight defines which permissions are more important. + # The higher number the more important. + PERM_WEIGHTS = { + 'repository.none': 0, + 'repository.read': 1, + 'repository.write': 3, + 'repository.admin': 4, + + 'group.none': 0, + 'group.read': 1, + 'group.write': 3, + 'group.admin': 4, + + 'usergroup.none': 0, + 'usergroup.read': 1, + 'usergroup.write': 3, + 'usergroup.admin': 4, + 'hg.repogroup.create.false': 0, + 'hg.repogroup.create.true': 1, + + 'hg.usergroup.create.false': 0, + 'hg.usergroup.create.true': 1, + + 'hg.fork.none': 0, + 'hg.fork.repository': 1, + 'hg.create.none': 0, + 'hg.create.repository': 1 + } + + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session().query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_user_group_perms(cls, default_user_id): + q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ + .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ + .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserUserGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.repository) + + +class UserUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'user_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + user_group = relationship('UserGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, user_group, permission): + n = cls() + n.user = user + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.user_group) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.permission) + + +class UserGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) + + +class UserGroupUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_group_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), + CheckConstraint('target_user_group_id != user_group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') + user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') + permission = relationship('Permission') + + @classmethod + def create(cls, target_user_group, user_group, permission): + n = cls() + n.target_user_group = target_user_group + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.target_user_group, self.user_group) + + +class UserGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + +class UserGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + Index('key_idx', 'cache_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + # cache_id, not used + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + # cache_key as created by _get_cache_key + cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + # cache_args is a repo_name + cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + # instance sets cache_active True when it is caching, + # other instances set cache_active to False to indicate that this cache is invalid + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, repo_name=''): + self.cache_key = cache_key + self.cache_args = repo_name + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, + self.cache_id, self.cache_key, self.cache_active) + + def _cache_key_partition(self): + prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) + return prefix, repo_name, suffix + + def get_prefix(self): + """ + get prefix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[0] + + def get_suffix(self): + """ + get suffix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[2] + + @classmethod + def clear_cache(cls): + """ + Delete all cache keys from database. + Should only be run when all instances are down and all entries thus stale. + """ + cls.query().delete() + Session().commit() + + @classmethod + def _get_cache_key(cls, key): + """ + Wrapper for generating a unique cache key for this instance and "key". + key must / will start with a repo_name which will be stored in .cache_args . + """ + import rhodecode + prefix = rhodecode.CONFIG.get('instance_id', '') + return "%s%s" % (prefix, key) + + @classmethod + def set_invalidate(cls, repo_name, delete=False): + """ + Mark all caches of a repo as invalid in the database. + """ + inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() + log.debug('for repo %s got %s invalidation objects' % (repo_name, inv_objs)) + try: + for inv_obj in inv_objs: + log.debug('marking %s key for invalidation based on repo_name=%s' + % (inv_obj, safe_str(repo_name))) + if delete: + Session().delete(inv_obj) + else: + inv_obj.cache_active = False + Session().add(inv_obj) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + + @classmethod + def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): + """ + Mark this cache key as active and currently cached. + Return True if the existing cache registration still was valid. + Return False to indicate that it had been invalidated and caches should be refreshed. + """ + + key = (repo_name + '_' + kind) if kind else repo_name + cache_key = cls._get_cache_key(key) + + if valid_cache_keys and cache_key in valid_cache_keys: + return True + + try: + inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() + if not inv_obj: + inv_obj = CacheInvalidation(cache_key, repo_name) + was_valid = inv_obj.cache_active + inv_obj.cache_active = True + Session().add(inv_obj) + Session().commit() + return was_valid + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + return False + + @classmethod + def get_valid_cache_keys(cls): + """ + Return opaque object with information of which caches still are valid + and can be used without checking for invalidation. + """ + return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + Index('cc_revision_idx', 'revision'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + line_no = Column('line_no', Unicode(10), nullable=True) + hl_lines = Column('hl_lines', Unicode(512), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', UnicodeText(25000), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_users(cls, revision=None, pull_request_id=None): + """ + Returns user associated with this ChangesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + q = Session().query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' + + STATUSES = [ + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), + ] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + # values for .status + STATUS_NEW = u'new' + STATUS_OPEN = u'open' + STATUS_CLOSED = u'closed' + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', UnicodeText(10240), nullable=True) + status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + @property + def org_ref_parts(self): + return self.org_ref.split(':') + + @property + def other_ref_parts(self): + return self.other_ref.split(':') + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers', + cascade="all, delete, delete-orphan") + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + statuses = relationship('ChangesetStatus') + comments = relationship('ChangesetComment', + cascade="all, delete, delete-orphan") + + def is_closed(self): + return self.status == self.STATUS_CLOSED + + @property + def last_review_status(self): + return self.statuses[-1].status if self.statuses else '' + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', UnicodeText(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self)\ + .order_by(UserNotification.user_id.asc()).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session().add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session().add(self) + + +class Gist(Base, BaseModel): + __tablename__ = 'gists' + __table_args__ = ( + Index('g_gist_access_id_idx', 'gist_access_id'), + Index('g_created_on_idx', 'created_on'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + GIST_PUBLIC = u'public' + GIST_PRIVATE = u'private' + DEFAULT_FILENAME = u'gistfile1.txt' + + gist_id = Column('gist_id', Integer(), primary_key=True) + gist_access_id = Column('gist_access_id', Unicode(250)) + gist_description = Column('gist_description', UnicodeText(1024)) + gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) + gist_expires = Column('gist_expires', Float(53), nullable=False) + gist_type = Column('gist_type', Unicode(128), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + owner = relationship('User') + + def __repr__(self): + return '' % (self.gist_type, self.gist_access_id) + + @classmethod + def get_or_404(cls, id_): + res = cls.query().filter(cls.gist_access_id == id_).scalar() + if not res: + raise HTTPNotFound + return res + + @classmethod + def get_by_access_id(cls, gist_access_id): + return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() + + def gist_url(self): + import rhodecode + alias_url = rhodecode.CONFIG.get('gist_alias_url') + if alias_url: + return alias_url.replace('{gistid}', self.gist_access_id) + + from pylons import url + return url('gist', gist_id=self.gist_access_id, qualified=True) + + @classmethod + def base_path(cls): + """ + Returns base path when all gists are stored + + :param cls: + """ + from rhodecode.model.gist import GIST_STORE_LOC + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == URL_SEP) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return os.path.join(q.one().ui_value, GIST_STORE_LOC) + + def get_api_data(self): + """ + Common function for generating gist related data for API + """ + gist = self + data = dict( + gist_id=gist.gist_id, + type=gist.gist_type, + access_id=gist.gist_access_id, + description=gist.gist_description, + url=gist.gist_url(), + expires=gist.gist_expires, + created_on=gist.created_on, + ) + return data + + def __json__(self): + data = dict( + ) + data.update(self.get_api_data()) + return data + ## SCM functions + + @property + def scm_instance(self): + from rhodecode.lib.vcs import get_repo + base_path = self.base_path() + return get_repo(os.path.join(*map(safe_str, + [base_path, self.gist_access_id]))) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/schema/db_2_2_3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/schema/db_2_2_3.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,2494 @@ +# -*- 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 . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import time +import logging +import datetime +import traceback +import hashlib +import collections +import functools + +from sqlalchemy import * +from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates +from sqlalchemy.exc import DatabaseError +from beaker.cache import cache_region, region_invalidate +from webob.exc import HTTPNotFound + +from pylons.i18n.translation import lazy_ugettext as _ + +from rhodecode.lib.vcs import get_backend +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.utils.lazy import LazyProperty +from rhodecode.lib.vcs.backends.base import EmptyChangeset + +from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ + safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \ + get_clone_url +from rhodecode.lib.compat import json +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model.meta import Base, Session + +URL_SEP = '/' +log = logging.getLogger(__name__) + +#============================================================================== +# BASE CLASSES +#============================================================================== + +_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() + + +class BaseModel(object): + """ + Base Model for all classess + """ + + @classmethod + def _get_keys(cls): + """return column names for this model """ + return class_mapper(cls).c.keys() + + def get_dict(self): + """ + return dict with keys and values corresponding + to this model data """ + + d = {} + for k in self._get_keys(): + d[k] = getattr(self, k) + + # also use __json__() if present to get additional fields + _json_attr = getattr(self, '__json__', None) + if _json_attr: + # update with attributes from __json__ + if callable(_json_attr): + _json_attr = _json_attr() + for k, val in _json_attr.iteritems(): + d[k] = val + return d + + def get_appstruct(self): + """return list with keys and values tupples corresponding + to this model data """ + + l = [] + for k in self._get_keys(): + l.append((k, getattr(self, k),)) + return l + + def populate_obj(self, populate_dict): + """populate model with data from given populate_dict""" + + for k in self._get_keys(): + if k in populate_dict: + setattr(self, k, populate_dict[k]) + + @classmethod + def query(cls): + return Session().query(cls) + + @classmethod + def get(cls, id_): + if id_: + return cls.query().get(id_) + + @classmethod + def get_or_404(cls, id_): + try: + id_ = int(id_) + except (TypeError, ValueError): + raise HTTPNotFound + + res = cls.query().get(id_) + if not res: + raise HTTPNotFound + return res + + @classmethod + def getAll(cls): + # deprecated and left for backward compatibility + return cls.get_all() + + @classmethod + def get_all(cls): + return cls.query().all() + + @classmethod + def delete(cls, id_): + obj = cls.query().get(id_) + Session().delete(obj) + + def __repr__(self): + if hasattr(self, '__unicode__'): + # python repr needs to return str + try: + return safe_str(self.__unicode__()) + except UnicodeDecodeError: + pass + return '' % (self.__class__.__name__) + + +class RhodeCodeSetting(Base, BaseModel): + __tablename__ = 'rhodecode_settings' + __table_args__ = ( + UniqueConstraint('app_settings_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + SETTINGS_TYPES = { + 'str': safe_str, + 'int': safe_int, + 'unicode': safe_unicode, + 'bool': str2bool, + 'list': functools.partial(aslist, sep=',') + } + DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions' + + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type + + @validates('_app_settings_value') + def validate_settings_value(self, key, val): + assert type(val) == unicode + return val + + @hybrid_property + def app_settings_value(self): + v = self._app_settings_value + _type = self.app_settings_type + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + return converter(v) + + @app_settings_value.setter + def app_settings_value(self, val): + """ + Setter that will always make sure we use unicode in app_settings_value + + :param val: + """ + self._app_settings_value = safe_unicode(val) + + @hybrid_property + def app_settings_type(self): + return self._app_settings_type + + @app_settings_type.setter + def app_settings_type(self, val): + if val not in self.SETTINGS_TYPES: + raise Exception('type must be one of %s got %s' + % (self.SETTINGS_TYPES.keys(), val)) + self._app_settings_type = val + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % ( + self.__class__.__name__, + self.app_settings_name, self.app_settings_value, self.app_settings_type + ) + + @classmethod + def get_by_name(cls, key): + return cls.query()\ + .filter(cls.app_settings_name == key).scalar() + + @classmethod + def get_by_name_or_create(cls, key, val='', type='unicode'): + res = cls.get_by_name(key) + if not res: + res = cls(key, val, type) + return res + + @classmethod + def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): + """ + Creates or updates RhodeCode setting. If updates is triggered it will only + update parameters that are explicityl set Optional instance will be skipped + + :param key: + :param val: + :param type: + :return: + """ + res = cls.get_by_name(key) + if not res: + val = Optional.extract(val) + type = Optional.extract(type) + res = cls(key, val, type) + else: + res.app_settings_name = key + if not isinstance(val, Optional): + # update if set + res.app_settings_value = val + if not isinstance(type, Optional): + # update if set + res.app_settings_type = type + return res + + @classmethod + def get_app_settings(cls, cache=False): + + ret = cls.query() + + if cache: + ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) + + if not ret: + raise Exception('Could not get application settings !') + settings = {} + for each in ret: + settings['rhodecode_' + each.app_settings_name] = \ + each.app_settings_value + + return settings + + @classmethod + def get_auth_plugins(cls, cache=False): + auth_plugins = cls.get_by_name("auth_plugins").app_settings_value + return auth_plugins + + @classmethod + def get_auth_settings(cls, cache=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('auth_')).all() + fd = {} + for row in ret: + fd.update({row.app_settings_name: row.app_settings_value}) + + return fd + + @classmethod + def get_default_repo_settings(cls, cache=False, strip_prefix=False): + ret = cls.query()\ + .filter(cls.app_settings_name.startswith('default_')).all() + fd = {} + for row in ret: + key = row.app_settings_name + if strip_prefix: + key = remove_prefix(key, prefix='default_') + fd.update({key: row.app_settings_value}) + + return fd + + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': safe_unicode(platform.platform()), + 'rhodecode_version': rhodecode.__version__, + 'git_version': safe_unicode(check_git_version()), + 'git_path': rhodecode.CONFIG.get('git_path') + } + return info + + +class RhodeCodeUi(Base, BaseModel): + __tablename__ = 'rhodecode_ui' + __table_args__ = ( + UniqueConstraint('ui_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + HOOK_UPDATE = 'changegroup.update' + HOOK_REPO_SIZE = 'changegroup.repo_size' + HOOK_PUSH = 'changegroup.push_logger' + HOOK_PRE_PUSH = 'prechangegroup.pre_push' + HOOK_PULL = 'outgoing.pull_logger' + HOOK_PRE_PULL = 'preoutgoing.pre_pull' + + ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) + + # def __init__(self, section='', key='', value=''): + # self.ui_section = section + # self.ui_key = key + # self.ui_value = value + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.ui_key == key).scalar() + + @classmethod + def get_builtin_hooks(cls): + q = cls.query() + q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + return q.all() + + @classmethod + def get_custom_hooks(cls): + q = cls.query() + q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, + cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, + cls.HOOK_PULL, cls.HOOK_PRE_PULL])) + q = q.filter(cls.ui_section == 'hooks') + return q.all() + + @classmethod + def get_repos_location(cls): + return cls.get_by_key('/').ui_value + + @classmethod + def create_or_update_hook(cls, key, val): + new_ui = cls.get_by_key(key) or cls() + new_ui.ui_section = 'hooks' + new_ui.ui_active = True + new_ui.ui_key = key + new_ui.ui_value = val + + Session().add(new_ui) + + def __repr__(self): + return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section, + self.ui_key, self.ui_value) + + +class User(Base, BaseModel): + __tablename__ = 'users' + __table_args__ = ( + UniqueConstraint('username'), UniqueConstraint('email'), + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + DEFAULT_USER = 'default' + DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}' + + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) + name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) + extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data + + user_log = relationship('UserLog') + user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') + + repositories = relationship('Repository') + user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') + followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') + + repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') + repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') + + group_member = relationship('UserGroupMember', cascade='all') + + notifications = relationship('UserNotification', cascade='all') + # notifications assigned to this user + user_created_notifications = relationship('Notification', cascade='all') + # comments created by this user + user_comments = relationship('ChangesetComment', cascade='all') + #extra emails for this user + user_emails = relationship('UserEmailMap', cascade='all') + #extra api keys + user_api_keys = relationship('UserApiKeys', cascade='all') + + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + @property + def firstname(self): + # alias for future + return self.name + + @property + def emails(self): + other = UserEmailMap.query().filter(UserEmailMap.user==self).all() + return [self.email] + [x.email for x in other] + + @property + def api_keys(self): + other = UserApiKeys.query().filter(UserApiKeys.user==self).all() + return [self.api_key] + [x.api_key for x in other] + + @property + def ip_addresses(self): + ret = UserIpMap.query().filter(UserIpMap.user == self).all() + return [x.ip_addr for x in ret] + + @property + def username_and_name(self): + return '%s (%s %s)' % (self.username, self.firstname, self.lastname) + + @property + def full_name(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def full_name_or_username(self): + return ('%s %s' % (self.firstname, self.lastname) + if (self.firstname and self.lastname) else self.username) + + @property + def full_contact(self): + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) + + @property + def short_contact(self): + return '%s %s' % (self.firstname, self.lastname) + + @property + def is_admin(self): + return self.admin + + @property + def AuthUser(self): + """ + Returns instance of AuthUser for this user + """ + from rhodecode.lib.auth import AuthUser + return AuthUser(user_id=self.user_id, api_key=self.api_key, + username=self.username) + + @hybrid_property + def user_data(self): + if not self._user_data: + return {} + + try: + return json.loads(self._user_data) + except TypeError: + return {} + + @user_data.setter + def user_data(self, val): + try: + self._user_data = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + + @classmethod + def get_by_username(cls, username, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.username.ilike(username)) + else: + q = cls.query().filter(cls.username == username) + + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(username) + ) + ) + return q.scalar() + + @classmethod + def get_by_api_key(cls, api_key, cache=False, fallback=True): + q = cls.query().filter(cls.api_key == api_key) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_api_key_%s" % api_key)) + res = q.scalar() + + if fallback and not res: + #fallback to additional keys + _res = UserApiKeys.query()\ + .filter(UserApiKeys.api_key == api_key)\ + .filter(or_(UserApiKeys.expires == -1, + UserApiKeys.expires >= time.time()))\ + .first() + if _res: + res = _res.user + return res + + @classmethod + def get_by_email(cls, email, case_insensitive=False, cache=False): + if case_insensitive: + q = cls.query().filter(cls.email.ilike(email)) + else: + q = cls.query().filter(cls.email == email) + + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_key_%s" % email)) + + ret = q.scalar() + if ret is None: + q = UserEmailMap.query() + # try fetching in alternate email map + if case_insensitive: + q = q.filter(UserEmailMap.email.ilike(email)) + else: + q = q.filter(UserEmailMap.email == email) + q = q.options(joinedload(UserEmailMap.user)) + if cache: + q = q.options(FromCache("sql_cache_short", + "get_email_map_key_%s" % email)) + ret = getattr(q.scalar(), 'user', None) + + return ret + + @classmethod + def get_from_cs_author(cls, author): + """ + Tries to get User objects out of commit author string + + :param author: + """ + from rhodecode.lib.helpers import email, author_name + # Valid email in the attribute passed, see if they're in the system + _email = email(author) + if _email: + user = cls.get_by_email(_email, case_insensitive=True) + if user: + return user + # Maybe we can match by username? + _author = author_name(author) + user = cls.get_by_username(_author, case_insensitive=True) + if user: + return user + + def update_lastlogin(self): + """Update user lastlogin""" + self.last_login = datetime.datetime.now() + Session().add(self) + log.debug('updated user %s lastlogin' % self.username) + + @classmethod + def get_first_admin(cls): + user = User.query().filter(User.admin == True).first() + if user is None: + raise Exception('Missing administrative account!') + return user + + @classmethod + def get_default_user(cls, cache=False): + user = User.get_by_username(User.DEFAULT_USER, cache=cache) + if user is None: + raise Exception('Missing default account!') + return user + + def get_api_data(self): + """ + Common function for generating user related data for API + """ + user = self + data = dict( + user_id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + emails=user.emails, + api_key=user.api_key, + api_keys=user.api_keys, + active=user.active, + admin=user.admin, + extern_type=user.extern_type, + extern_name=user.extern_name, + last_login=user.last_login, + ip_addresses=user.ip_addresses + ) + return data + + def __json__(self): + data = dict( + full_name=self.full_name, + full_name_or_username=self.full_name_or_username, + short_contact=self.short_contact, + full_contact=self.full_contact + ) + data.update(self.get_api_data()) + return data + + +class UserApiKeys(Base, BaseModel): + __tablename__ = 'user_api_keys' + __table_args__ = ( + Index('uak_api_key_idx', 'api_key'), + Index('uak_api_key_expires_idx', 'api_key', 'expires'), + UniqueConstraint('api_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True) + description = Column('description', UnicodeText(1024)) + expires = Column('expires', Float(53), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user = relationship('User', lazy='joined') + + @property + def expired(self): + if self.expires == -1: + return False + return time.time() > self.expires + + +class UserEmailMap(Base, BaseModel): + __tablename__ = 'user_email_map' + __table_args__ = ( + Index('uem_email_idx', 'email'), + UniqueConstraint('email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + user = relationship('User', lazy='joined') + + @validates('_email') + def validate_email(self, key, email): + # check if this email is not main one + main_email = Session().query(User).filter(User.email == email).scalar() + if main_email is not None: + raise AttributeError('email %s is present is user table' % email) + return email + + @hybrid_property + def email(self): + return self._email + + @email.setter + def email(self, val): + self._email = val.lower() if val else None + + +class UserIpMap(Base, BaseModel): + __tablename__ = 'user_ip_map' + __table_args__ = ( + UniqueConstraint('user_id', 'ip_addr'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + active = Column("active", Boolean(), nullable=True, unique=None, default=True) + user = relationship('User', lazy='joined') + + @classmethod + def _get_ip_range(cls, ip_addr): + from rhodecode.lib import ipaddr + net = ipaddr.IPNetwork(address=ip_addr) + return [str(net.network), str(net.broadcast)] + + def __json__(self): + return dict( + ip_addr=self.ip_addr, + ip_range=self._get_ip_range(self.ip_addr) + ) + + def __unicode__(self): + return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__, + self.user_id, self.ip_addr) + +class UserLog(Base, BaseModel): + __tablename__ = 'user_logs' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) + repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None) + action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.repository_name, + self.action) + + @property + def action_as_day(self): + return datetime.date(*self.action_date.timetuple()[:3]) + + user = relationship('User') + repository = relationship('Repository', cascade='') + + +class UserGroup(Base, BaseModel): + __tablename__ = 'users_groups' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) + users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) + inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data + + members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + users_group_to_perm = relationship('UserGroupToPerm', cascade='all') + users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') + user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') + + user = relationship('User') + + @hybrid_property + def group_data(self): + if not self._group_data: + return {} + + try: + return json.loads(self._group_data) + except TypeError: + return {} + + @group_data.setter + def group_data(self, val): + try: + self._group_data = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, + self.users_group_id, + self.users_group_name) + + @classmethod + def get_by_group_name(cls, group_name, cache=False, + case_insensitive=False): + if case_insensitive: + q = cls.query().filter(cls.users_group_name.ilike(group_name)) + else: + q = cls.query().filter(cls.users_group_name == group_name) + if cache: + q = q.options(FromCache( + "sql_cache_short", + "get_user_%s" % _hash_key(group_name) + ) + ) + return q.scalar() + + @classmethod + def get(cls, user_group_id, cache=False): + user_group = cls.query() + if cache: + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self + + data = dict( + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + group_description=user_group.user_group_description, + active=user_group.users_group_active, + owner=user_group.user.username, + ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members + + return data + + +class UserGroupMember(Base, BaseModel): + __tablename__ = 'users_groups_members' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + + user = relationship('User', lazy='joined') + users_group = relationship('UserGroup') + + def __init__(self, gr_id='', u_id=''): + self.users_group_id = gr_id + self.user_id = u_id + + +class RepositoryField(Base, BaseModel): + __tablename__ = 'repositories_fields' + __table_args__ = ( + UniqueConstraint('repository_id', 'field_key'), # no-multi field + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields + + repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + field_key = Column("field_key", String(250, convert_unicode=False)) + field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False) + field_type = Column("field_type", String(256), nullable=False, unique=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repository = relationship('Repository') + + @property + def field_key_prefixed(self): + return 'ex_%s' % self.field_key + + @classmethod + def un_prefix_key(cls, key): + if key.startswith(cls.PREFIX): + return key[len(cls.PREFIX):] + return key + + @classmethod + def get_by_key_name(cls, key, repo): + row = cls.query()\ + .filter(cls.repository == repo)\ + .filter(cls.field_key == key).scalar() + return row + + +class Repository(Base, BaseModel): + __tablename__ = 'repositories' + __table_args__ = ( + UniqueConstraint('repo_name'), + Index('r_repo_name_idx', 'repo_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}' + DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}' + + STATE_CREATED = 'repo_state_created' + STATE_PENDING = 'repo_state_pending' + STATE_ERROR = 'repo_state_error' + + repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + repo_state = Column("repo_state", String(255), nullable=True) + + clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + private = Column("private", Boolean(), nullable=True, unique=None, default=None) + enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) + enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) + description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data + + fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) + + user = relationship('User') + fork = relationship('Repository', remote_side=repo_id) + group = relationship('RepoGroup') + repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') + stats = relationship('Statistics', cascade='all', uselist=False) + + followers = relationship('UserFollowing', + primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', + cascade='all') + extra_fields = relationship('RepositoryField', + cascade="all, delete, delete-orphan") + + logs = relationship('UserLog') + comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") + + pull_requests_org = relationship('PullRequest', + primaryjoin='PullRequest.org_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + pull_requests_other = relationship('PullRequest', + primaryjoin='PullRequest.other_repo_id==Repository.repo_id', + cascade="all, delete, delete-orphan") + + def __unicode__(self): + return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, + safe_unicode(self.repo_name)) + + @hybrid_property + def landing_rev(self): + # always should return [rev_type, rev] + if self._landing_revision: + _rev_info = self._landing_revision.split(':') + if len(_rev_info) < 2: + _rev_info.insert(0, 'rev') + return [_rev_info[0], _rev_info[1]] + return [None, None] + + @landing_rev.setter + def landing_rev(self, val): + if ':' not in val: + raise ValueError('value must be delimited with `:` and consist ' + 'of :, got %s instead' % val) + self._landing_revision = val + + @hybrid_property + def locked(self): + # always should return [user_id, timelocked] + if self._locked: + _lock_info = self._locked.split(':') + return int(_lock_info[0]), _lock_info[1] + return [None, None] + + @locked.setter + def locked(self, val): + if val and isinstance(val, (list, tuple)): + self._locked = ':'.join(map(str, val)) + else: + self._locked = None + + @hybrid_property + def changeset_cache(self): + from rhodecode.lib.vcs.backends.base import EmptyChangeset + dummy = EmptyChangeset().__json__() + if not self._changeset_cache: + return dummy + try: + return json.loads(self._changeset_cache) + except TypeError: + return dummy + + @changeset_cache.setter + def changeset_cache(self, val): + try: + self._changeset_cache = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def normalize_repo_name(cls, repo_name): + """ + Normalizes os specific repo_name to the format internally stored inside + dabatabase using URL_SEP + + :param cls: + :param repo_name: + """ + return cls.url_sep().join(repo_name.split(os.sep)) + + @classmethod + def get_by_repo_name(cls, repo_name): + q = Session().query(cls).filter(cls.repo_name == repo_name) + q = q.options(joinedload(Repository.fork))\ + .options(joinedload(Repository.user))\ + .options(joinedload(Repository.group)) + return q.scalar() + + @classmethod + def get_by_full_path(cls, repo_full_path): + repo_name = repo_full_path.split(cls.base_path(), 1)[-1] + repo_name = cls.normalize_repo_name(repo_name) + return cls.get_by_repo_name(repo_name.strip(URL_SEP)) + + @classmethod + def get_repo_forks(cls, repo_id): + return cls.query().filter(Repository.fork_id == repo_id) + + @classmethod + def base_path(cls): + """ + Returns base path when all repos are stored + + :param cls: + """ + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == cls.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def forks(self): + """ + Return forks of this repo + """ + return Repository.get_repo_forks(self.repo_id) + + @property + def parent(self): + """ + Returns fork parent + """ + return self.fork + + @property + def just_name(self): + return self.repo_name.split(Repository.url_sep())[-1] + + @property + def groups_with_parents(self): + groups = [] + if self.group is None: + return groups + + cur_gr = self.group + groups.insert(0, cur_gr) + while 1: + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + groups.insert(0, gr) + + return groups + + @property + def groups_and_repo(self): + return self.groups_with_parents, self.just_name, self.repo_name + + @LazyProperty + def repo_path(self): + """ + Returns base full path for that repository means where it actually + exists on a filesystem + """ + q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + Repository.url_sep()) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return q.one().ui_value + + @property + def repo_full_path(self): + p = [self.repo_path] + # we need to split the name by / since this is how we store the + # names in the database, but that eventually needs to be converted + # into a valid system path + p += self.repo_name.split(Repository.url_sep()) + return os.path.join(*map(safe_unicode, p)) + + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + + def get_new_name(self, repo_name): + """ + returns new full repository name based on assigned group and new new + + :param group_name: + """ + path_prefix = self.group.full_path_splitted if self.group else [] + return Repository.url_sep().join(path_prefix + [repo_name]) + + @property + def _ui(self): + """ + Creates an db based ui object for this repository + """ + from rhodecode.lib.utils import make_ui + return make_ui('db', clear_session=False) + + @classmethod + def is_valid(cls, repo_name): + """ + returns True if given repo name is a valid filesystem repository + + :param cls: + :param repo_name: + """ + from rhodecode.lib.utils import is_valid_repo + + return is_valid_repo(repo_name, cls.base_path()) + + def get_api_data(self): + """ + Common function for generating repo api data + + """ + repo = self + data = dict( + repo_id=repo.repo_id, + repo_name=repo.repo_name, + repo_type=repo.repo_type, + clone_uri=repo.clone_uri, + private=repo.private, + created_on=repo.created_on, + description=repo.description, + landing_rev=repo.landing_rev, + owner=repo.user.username, + fork_of=repo.fork.repo_name if repo.fork else None, + enable_statistics=repo.enable_statistics, + enable_locking=repo.enable_locking, + enable_downloads=repo.enable_downloads, + last_changeset=repo.changeset_cache, + locked_by=User.get(self.locked[0]).get_api_data() \ + if self.locked[0] else None, + locked_date=time_to_datetime(self.locked[1]) \ + if self.locked[1] else None + ) + rc_config = RhodeCodeSetting.get_app_settings() + repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) + if repository_fields: + for f in self.extra_fields: + data[f.field_key_prefixed] = f.field_value + + return data + + @classmethod + def lock(cls, repo, user_id, lock_time=None): + if not lock_time: + lock_time = time.time() + repo.locked = [user_id, lock_time] + Session().add(repo) + Session().commit() + + @classmethod + def unlock(cls, repo): + repo.locked = None + Session().add(repo) + Session().commit() + + @classmethod + def getlock(cls, repo): + return repo.locked + + @property + def last_db_change(self): + return self.updated_on + + def clone_url(self, **override): + from pylons import url + qualified_home_url = url('home', qualified=True) + + uri_tmpl = None + if 'with_id' in override: + uri_tmpl = self.DEFAULT_CLONE_URI_ID + del override['with_id'] + + if 'uri_tmpl' in override: + uri_tmpl = override['uri_tmpl'] + del override['uri_tmpl'] + + # we didn't override our tmpl from **overrides + if not uri_tmpl: + uri_tmpl = self.DEFAULT_CLONE_URI + try: + from pylons import tmpl_context as c + uri_tmpl = c.clone_uri_tmpl + except Exception: + # in any case if we call this outside of request context, + # ie, not having tmpl_context set up + pass + + return get_clone_url(uri_tmpl=uri_tmpl, + qualifed_home_url=qualified_home_url, + repo_name=self.repo_name, + repo_id=self.repo_id, **override) + + def set_state(self, state): + self.repo_state = state + Session().add(self) + #========================================================================== + # SCM PROPERTIES + #========================================================================== + + def get_changeset(self, rev=None): + return get_changeset_safe(self.scm_instance, rev) + + def get_landing_changeset(self): + """ + Returns landing changeset, or if that doesn't exist returns the tip + """ + _rev_type, _rev = self.landing_rev + cs = self.get_changeset(_rev) + if isinstance(cs, EmptyChangeset): + return self.get_changeset() + return cs + + def update_changeset_cache(self, cs_cache=None): + """ + Update cache of last changeset for repository, keys should be:: + + short_id + raw_id + revision + message + date + author + + :param cs_cache: + """ + from rhodecode.lib.vcs.backends.base import BaseChangeset + if cs_cache is None: + cs_cache = EmptyChangeset() + # use no-cache version here + scm_repo = self.scm_instance_no_cache() + if scm_repo: + cs_cache = scm_repo.get_changeset() + + if isinstance(cs_cache, BaseChangeset): + cs_cache = cs_cache.__json__() + + if (cs_cache != self.changeset_cache or not self.changeset_cache): + _default = datetime.datetime.fromtimestamp(0) + last_change = cs_cache.get('date') or _default + log.debug('updated repo %s with new cs cache %s' + % (self.repo_name, cs_cache)) + self.updated_on = last_change + self.changeset_cache = cs_cache + Session().add(self) + Session().commit() + else: + log.debug('Skipping repo:%s already with latest changes' + % self.repo_name) + + @property + def tip(self): + return self.get_changeset('tip') + + @property + def author(self): + return self.tip.author + + @property + def last_change(self): + return self.scm_instance.last_change + + def get_comments(self, revisions=None): + """ + Returns comments for this repository grouped by revisions + + :param revisions: filter query by revisions only + """ + cmts = ChangesetComment.query()\ + .filter(ChangesetComment.repo == self) + if revisions: + cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) + grouped = collections.defaultdict(list) + for cmt in cmts.all(): + grouped[cmt.revision].append(cmt) + return grouped + + def statuses(self, revisions=None): + """ + Returns statuses for this repository + + :param revisions: list of revisions to get statuses for + """ + + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == self)\ + .filter(ChangesetStatus.version == 0) + if revisions: + statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) + grouped = {} + + #maybe we have open new pullrequest without a status ? + stat = ChangesetStatus.STATUS_UNDER_REVIEW + status_lbl = ChangesetStatus.get_status_lbl(stat) + for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): + for rev in pr.revisions: + pr_id = pr.pull_request_id + pr_repo = pr.other_repo.repo_name + grouped[rev] = [stat, status_lbl, pr_id, pr_repo] + + for stat in statuses.all(): + pr_id = pr_repo = None + if stat.pull_request: + pr_id = stat.pull_request.pull_request_id + pr_repo = stat.pull_request.other_repo.repo_name + grouped[stat.revision] = [str(stat.status), stat.status_lbl, + pr_id, pr_repo] + return grouped + + def _repo_size(self): + from rhodecode.lib import helpers as h + log.debug('calculating repository size...') + return h.format_byte_size(self.scm_instance.size) + + #========================================================================== + # SCM CACHE INSTANCE + #========================================================================== + + def set_invalidate(self): + """ + Mark caches of this repo as invalid. + """ + CacheInvalidation.set_invalidate(self.repo_name) + + def scm_instance_no_cache(self): + return self.__get_instance() + + @property + def scm_instance(self): + import rhodecode + full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) + if full_cache: + return self.scm_instance_cached() + return self.__get_instance() + + def scm_instance_cached(self, valid_cache_keys=None): + @cache_region('long_term') + def _c(repo_name): + return self.__get_instance() + rn = self.repo_name + + valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) + if not valid: + log.debug('Cache for %s invalidated, getting new object' % (rn)) + region_invalidate(_c, None, rn) + else: + log.debug('Getting obj for %s from cache' % (rn)) + return _c(rn) + + def __get_instance(self): + repo_full_path = self.repo_full_path + try: + alias = get_scm(repo_full_path)[0] + log.debug('Creating instance of %s repository from %s' + % (alias, repo_full_path)) + backend = get_backend(alias) + except VCSError: + log.error(traceback.format_exc()) + log.error('Perhaps this repository is in db and not in ' + 'filesystem run rescan repositories with ' + '"destroy old data " option from admin panel') + return + + if alias == 'hg': + + repo = backend(safe_str(repo_full_path), create=False, + baseui=self._ui) + # skip hidden web repository + if repo._get_hidden(): + return + else: + repo = backend(repo_full_path, create=False) + + return repo + + def __json__(self): + return dict(landing_rev = self.landing_rev) + +class RepoGroup(Base, BaseModel): + __tablename__ = 'groups' + __table_args__ = ( + UniqueConstraint('group_name', 'group_parent_id'), + CheckConstraint('group_id != group_parent_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + __mapper_args__ = {'order_by': 'group_name'} + + SEP = ' » ' + + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) + enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) + user = relationship('User') + + def __init__(self, group_name='', parent_group=None): + self.group_name = group_name + self.parent_group = parent_group + + def __unicode__(self): + return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, + self.group_name) + + @classmethod + def _generate_choice(cls, repo_group): + from webhelpers.html import literal as _literal + _name = lambda k: _literal(cls.SEP.join(k)) + return repo_group.group_id, _name(repo_group.full_path_splitted) + + @classmethod + def groups_choices(cls, groups=None, show_empty_group=True): + if not groups: + groups = cls.query().all() + + repo_groups = [] + if show_empty_group: + repo_groups = [('-1', u'-- %s --' % _('top level'))] + + repo_groups.extend([cls._generate_choice(x) for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0]) + return repo_groups + + @classmethod + def url_sep(cls): + return URL_SEP + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache( + "sql_cache_short", + "get_group_%s" % _hash_key(group_name) + ) + ) + return gr.scalar() + + @property + def parents(self): + parents_recursion_limit = 5 + groups = [] + if self.parent_group is None: + return groups + cur_gr = self.parent_group + groups.insert(0, cur_gr) + cnt = 0 + while 1: + cnt += 1 + gr = getattr(cur_gr, 'parent_group', None) + cur_gr = cur_gr.parent_group + if gr is None: + break + if cnt == parents_recursion_limit: + # this will prevent accidental infinit loops + log.error('group nested more than %s' % + parents_recursion_limit) + break + + groups.insert(0, gr) + return groups + + @property + def children(self): + return RepoGroup.query().filter(RepoGroup.parent_group == self) + + @property + def name(self): + return self.group_name.split(RepoGroup.url_sep())[-1] + + @property + def full_path(self): + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(RepoGroup.url_sep()) + + @property + def repositories(self): + return Repository.query()\ + .filter(Repository.group == self)\ + .order_by(Repository.repo_name) + + @property + def repositories_recursive_count(self): + cnt = self.repositories.count() + + def children_count(group): + cnt = 0 + for child in group.children: + cnt += child.repositories.count() + cnt += children_count(child) + return cnt + + return cnt + children_count(self) + + def _recursive_objects(self, include_repos=True): + all_ = [] + + def _get_members(root_gr): + if include_repos: + for r in root_gr.repositories: + all_.append(r) + childs = root_gr.children.all() + if childs: + for gr in childs: + all_.append(gr) + _get_members(gr) + + _get_members(self) + return [self] + all_ + + def recursive_groups_and_repos(self): + """ + Recursive return all groups, with repositories in those groups + """ + return self._recursive_objects() + + def recursive_groups(self): + """ + Returns all children groups for this group including children of children + """ + return self._recursive_objects(include_repos=False) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) + return RepoGroup.url_sep().join(path_prefix + [group_name]) + + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + + +class Permission(Base, BaseModel): + __tablename__ = 'permissions' + __table_args__ = ( + Index('p_perm_name_idx', 'permission_name'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + PERMS = [ + ('hg.admin', _('RhodeCode Administrator')), + + ('repository.none', _('Repository no access')), + ('repository.read', _('Repository read access')), + ('repository.write', _('Repository write access')), + ('repository.admin', _('Repository admin access')), + + ('group.none', _('Repository group no access')), + ('group.read', _('Repository group read access')), + ('group.write', _('Repository group write access')), + ('group.admin', _('Repository group admin access')), + + ('usergroup.none', _('User group no access')), + ('usergroup.read', _('User group read access')), + ('usergroup.write', _('User group write access')), + ('usergroup.admin', _('User group admin access')), + + ('hg.repogroup.create.false', _('Repository Group creation disabled')), + ('hg.repogroup.create.true', _('Repository Group creation enabled')), + + ('hg.usergroup.create.false', _('User Group creation disabled')), + ('hg.usergroup.create.true', _('User Group creation enabled')), + + ('hg.create.none', _('Repository creation disabled')), + ('hg.create.repository', _('Repository creation enabled')), + ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')), + ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')), + + ('hg.fork.none', _('Repository forking disabled')), + ('hg.fork.repository', _('Repository forking enabled')), + + ('hg.register.none', _('Registration disabled')), + ('hg.register.manual_activate', _('User Registration with manual account activation')), + ('hg.register.auto_activate', _('User Registration with automatic account activation')), + + ('hg.extern_activate.manual', _('Manual activation of external account')), + ('hg.extern_activate.auto', _('Automatic activation of external account')), + + ] + + #definition of system default permissions for DEFAULT user + DEFAULT_USER_PERMISSIONS = [ + 'repository.read', + 'group.read', + 'usergroup.read', + 'hg.create.repository', + 'hg.create.write_on_repogroup.true', + 'hg.fork.repository', + 'hg.register.manual_activate', + 'hg.extern_activate.auto', + ] + + # defines which permissions are more important higher the more important + # Weight defines which permissions are more important. + # The higher number the more important. + PERM_WEIGHTS = { + 'repository.none': 0, + 'repository.read': 1, + 'repository.write': 3, + 'repository.admin': 4, + + 'group.none': 0, + 'group.read': 1, + 'group.write': 3, + 'group.admin': 4, + + 'usergroup.none': 0, + 'usergroup.read': 1, + 'usergroup.write': 3, + 'usergroup.admin': 4, + 'hg.repogroup.create.false': 0, + 'hg.repogroup.create.true': 1, + + 'hg.usergroup.create.false': 0, + 'hg.usergroup.create.true': 1, + + 'hg.fork.none': 0, + 'hg.fork.repository': 1, + 'hg.create.none': 0, + 'hg.create.repository': 1 + } + + permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) + + @classmethod + def get_by_key(cls, key): + return cls.query().filter(cls.permission_name == key).scalar() + + @classmethod + def get_default_perms(cls, default_user_id): + q = Session().query(UserRepoToPerm, Repository, cls)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_group_perms(cls, default_user_id): + q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserRepoGroupToPerm.user_id == default_user_id) + + return q.all() + + @classmethod + def get_default_user_group_perms(cls, default_user_id): + q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ + .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ + .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ + .filter(UserUserGroupToPerm.user_id == default_user_id) + + return q.all() + + +class UserRepoToPerm(Base, BaseModel): + __tablename__ = 'repo_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'repository_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + repository = relationship('Repository') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository, permission): + n = cls() + n.user = user + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.repository) + + +class UserUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'user_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + user_group = relationship('UserGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, user_group, permission): + n = cls() + n.user = user + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.user_group) + + +class UserToPerm(Base, BaseModel): + __tablename__ = 'user_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + permission = relationship('Permission', lazy='joined') + + def __unicode__(self): + return u'<%s => %s >' % (self.user, self.permission) + + +class UserGroupRepoToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_to_perm' + __table_args__ = ( + UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + repository = relationship('Repository') + + @classmethod + def create(cls, users_group, repository, permission): + n = cls() + n.users_group = users_group + n.repository = repository + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.users_group, self.repository) + + +class UserGroupUserGroupToPerm(Base, BaseModel): + __tablename__ = 'user_group_user_group_to_perm' + __table_args__ = ( + UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), + CheckConstraint('target_user_group_id != user_group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + + target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') + user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') + permission = relationship('Permission') + + @classmethod + def create(cls, target_user_group, user_group, permission): + n = cls() + n.target_user_group = target_user_group + n.user_group = user_group + n.permission = permission + Session().add(n) + return n + + def __unicode__(self): + return u' %s >' % (self.target_user_group, self.user_group) + + +class UserGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'permission_id',), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + + +class UserRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'user_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', 'permission_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + user = relationship('User') + group = relationship('RepoGroup') + permission = relationship('Permission') + + @classmethod + def create(cls, user, repository_group, permission): + n = cls() + n.user = user + n.group = repository_group + n.permission = permission + Session().add(n) + return n + + +class UserGroupRepoGroupToPerm(Base, BaseModel): + __tablename__ = 'users_group_repo_group_to_perm' + __table_args__ = ( + UniqueConstraint('users_group_id', 'group_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UserGroup') + permission = relationship('Permission') + group = relationship('RepoGroup') + + @classmethod + def create(cls, user_group, repository_group, permission): + n = cls() + n.users_group = user_group + n.group = repository_group + n.permission = permission + Session().add(n) + return n + + +class Statistics(Base, BaseModel): + __tablename__ = 'statistics' + __table_args__ = ( + UniqueConstraint('repository_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) + stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) + commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data + commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data + languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data + + repository = relationship('Repository', single_parent=True) + + +class UserFollowing(Base, BaseModel): + __tablename__ = 'user_followings' + __table_args__ = ( + UniqueConstraint('user_id', 'follows_repository_id'), + UniqueConstraint('user_id', 'follows_user_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + + user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) + follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + + user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') + + follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') + follows_repository = relationship('Repository', order_by='Repository.repo_name') + + @classmethod + def get_repo_followers(cls, repo_id): + return cls.query().filter(cls.follows_repo_id == repo_id) + + +class CacheInvalidation(Base, BaseModel): + __tablename__ = 'cache_invalidation' + __table_args__ = ( + UniqueConstraint('cache_key'), + Index('key_idx', 'cache_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + # cache_id, not used + cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + # cache_key as created by _get_cache_key + cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + # cache_args is a repo_name + cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + # instance sets cache_active True when it is caching, + # other instances set cache_active to False to indicate that this cache is invalid + cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) + + def __init__(self, cache_key, repo_name=''): + self.cache_key = cache_key + self.cache_args = repo_name + self.cache_active = False + + def __unicode__(self): + return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, + self.cache_id, self.cache_key, self.cache_active) + + def _cache_key_partition(self): + prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) + return prefix, repo_name, suffix + + def get_prefix(self): + """ + get prefix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[0] + + def get_suffix(self): + """ + get suffix that might have been used in _get_cache_key to + generate self.cache_key. Only used for informational purposes + in repo_edit.html. + """ + # prefix, repo_name, suffix + return self._cache_key_partition()[2] + + @classmethod + def clear_cache(cls): + """ + Delete all cache keys from database. + Should only be run when all instances are down and all entries thus stale. + """ + cls.query().delete() + Session().commit() + + @classmethod + def _get_cache_key(cls, key): + """ + Wrapper for generating a unique cache key for this instance and "key". + key must / will start with a repo_name which will be stored in .cache_args . + """ + import rhodecode + prefix = rhodecode.CONFIG.get('instance_id', '') + return "%s%s" % (prefix, key) + + @classmethod + def set_invalidate(cls, repo_name, delete=False): + """ + Mark all caches of a repo as invalid in the database. + """ + inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() + log.debug('for repo %s got %s invalidation objects' + % (safe_str(repo_name), inv_objs)) + try: + for inv_obj in inv_objs: + log.debug('marking %s key for invalidation based on repo_name=%s' + % (inv_obj, safe_str(repo_name))) + if delete: + Session().delete(inv_obj) + else: + inv_obj.cache_active = False + Session().add(inv_obj) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + + @classmethod + def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): + """ + Mark this cache key as active and currently cached. + Return True if the existing cache registration still was valid. + Return False to indicate that it had been invalidated and caches should be refreshed. + """ + + key = (repo_name + '_' + kind) if kind else repo_name + cache_key = cls._get_cache_key(key) + + if valid_cache_keys and cache_key in valid_cache_keys: + return True + + try: + inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() + if not inv_obj: + inv_obj = CacheInvalidation(cache_key, repo_name) + was_valid = inv_obj.cache_active + inv_obj.cache_active = True + Session().add(inv_obj) + Session().commit() + return was_valid + except Exception: + log.error(traceback.format_exc()) + Session().rollback() + return False + + @classmethod + def get_valid_cache_keys(cls): + """ + Return opaque object with information of which caches still are valid + and can be used without checking for invalidation. + """ + return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) + + +class ChangesetComment(Base, BaseModel): + __tablename__ = 'changeset_comments' + __table_args__ = ( + Index('cc_revision_idx', 'revision'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + revision = Column('revision', String(40), nullable=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + line_no = Column('line_no', Unicode(10), nullable=True) + hl_lines = Column('hl_lines', Unicode(512), nullable=True) + f_path = Column('f_path', Unicode(1000), nullable=True) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) + text = Column('text', UnicodeText(25000), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") + pull_request = relationship('PullRequest', lazy='joined') + + @classmethod + def get_users(cls, revision=None, pull_request_id=None): + """ + Returns user associated with this ChangesetComment. ie those + who actually commented + + :param cls: + :param revision: + """ + q = Session().query(User)\ + .join(ChangesetComment.author) + if revision: + q = q.filter(cls.revision == revision) + elif pull_request_id: + q = q.filter(cls.pull_request_id == pull_request_id) + return q.all() + + +class ChangesetStatus(Base, BaseModel): + __tablename__ = 'changeset_statuses' + __table_args__ = ( + Index('cs_revision_idx', 'revision'), + Index('cs_version_idx', 'version'), + UniqueConstraint('repo_id', 'revision', 'version'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' + STATUS_APPROVED = 'approved' + STATUS_REJECTED = 'rejected' + STATUS_UNDER_REVIEW = 'under_review' + + STATUSES = [ + (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default + (STATUS_APPROVED, _("Approved")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_UNDER_REVIEW, _("Under Review")), + ] + + changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) + repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + revision = Column('revision', String(40), nullable=False) + status = Column('status', String(128), nullable=False, default=DEFAULT) + changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) + modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + version = Column('version', Integer(), nullable=False, default=0) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) + + author = relationship('User', lazy='joined') + repo = relationship('Repository') + comment = relationship('ChangesetComment', lazy='joined') + pull_request = relationship('PullRequest', lazy='joined') + + def __unicode__(self): + return u"<%s('%s:%s')>" % ( + self.__class__.__name__, + self.status, self.author + ) + + @classmethod + def get_status_lbl(cls, value): + return dict(cls.STATUSES).get(value) + + @property + def status_lbl(self): + return ChangesetStatus.get_status_lbl(self.status) + + +class PullRequest(Base, BaseModel): + __tablename__ = 'pull_requests' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + # values for .status + STATUS_NEW = u'new' + STATUS_OPEN = u'open' + STATUS_CLOSED = u'closed' + + pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) + title = Column('title', Unicode(256), nullable=True) + description = Column('description', UnicodeText(10240), nullable=True) + status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) + _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max + org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + org_ref = Column('org_ref', Unicode(256), nullable=False) + other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) + other_ref = Column('other_ref', Unicode(256), nullable=False) + + @hybrid_property + def revisions(self): + return self._revisions.split(':') + + @revisions.setter + def revisions(self, val): + self._revisions = ':'.join(val) + + @property + def org_ref_parts(self): + return self.org_ref.split(':') + + @property + def other_ref_parts(self): + return self.other_ref.split(':') + + author = relationship('User', lazy='joined') + reviewers = relationship('PullRequestReviewers', + cascade="all, delete, delete-orphan") + org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') + other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') + statuses = relationship('ChangesetStatus') + comments = relationship('ChangesetComment', + cascade="all, delete, delete-orphan") + + def is_closed(self): + return self.status == self.STATUS_CLOSED + + @property + def last_review_status(self): + return self.statuses[-1].status if self.statuses else '' + + def __json__(self): + return dict( + revisions=self.revisions + ) + + +class PullRequestReviewers(Base, BaseModel): + __tablename__ = 'pull_request_reviewers' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + def __init__(self, user=None, pull_request=None): + self.user = user + self.pull_request = pull_request + + pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) + pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) + + user = relationship('User') + pull_request = relationship('PullRequest') + + +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ( + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' + TYPE_REGISTRATION = u'registration' + TYPE_PULL_REQUEST = u'pull_request' + TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' + + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', UnicodeText(50000), nullable=True) + created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + type_ = Column('type', Unicode(256)) + + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', lazy='joined', + cascade="all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self)\ + .order_by(UserNotification.user_id.asc()).all()] + + @classmethod + def create(cls, created_by, subject, body, recipients, type_=None): + if type_ is None: + type_ = Notification.TYPE_MESSAGE + + notification = cls() + notification.created_by_user = created_by + notification.subject = subject + notification.body = body + notification.type_ = type_ + notification.created_on = datetime.datetime.now() + + for u in recipients: + assoc = UserNotification() + assoc.notification = notification + u.notifications.append(assoc) + Session().add(notification) + return notification + + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ( + UniqueConstraint('user_id', 'notification_id'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) + read = Column('read', Boolean, default=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', lazy="joined") + notification = relationship('Notification', lazy="joined", + order_by=lambda: Notification.created_on.desc(),) + + def mark_as_read(self): + self.read = True + Session().add(self) + + +class Gist(Base, BaseModel): + __tablename__ = 'gists' + __table_args__ = ( + Index('g_gist_access_id_idx', 'gist_access_id'), + Index('g_created_on_idx', 'created_on'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + GIST_PUBLIC = u'public' + GIST_PRIVATE = u'private' + DEFAULT_FILENAME = u'gistfile1.txt' + + gist_id = Column('gist_id', Integer(), primary_key=True) + gist_access_id = Column('gist_access_id', Unicode(250)) + gist_description = Column('gist_description', UnicodeText(1024)) + gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) + gist_expires = Column('gist_expires', Float(53), nullable=False) + gist_type = Column('gist_type', Unicode(128), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + owner = relationship('User') + + def __repr__(self): + return '' % (self.gist_type, self.gist_access_id) + + @classmethod + def get_or_404(cls, id_): + res = cls.query().filter(cls.gist_access_id == id_).scalar() + if not res: + raise HTTPNotFound + return res + + @classmethod + def get_by_access_id(cls, gist_access_id): + return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() + + def gist_url(self): + import rhodecode + alias_url = rhodecode.CONFIG.get('gist_alias_url') + if alias_url: + return alias_url.replace('{gistid}', self.gist_access_id) + + from pylons import url + return url('gist', gist_id=self.gist_access_id, qualified=True) + + @classmethod + def base_path(cls): + """ + Returns base path when all gists are stored + + :param cls: + """ + from rhodecode.model.gist import GIST_STORE_LOC + q = Session().query(RhodeCodeUi)\ + .filter(RhodeCodeUi.ui_key == URL_SEP) + q = q.options(FromCache("sql_cache_short", "repository_repo_path")) + return os.path.join(q.one().ui_value, GIST_STORE_LOC) + + def get_api_data(self): + """ + Common function for generating gist related data for API + """ + gist = self + data = dict( + gist_id=gist.gist_id, + type=gist.gist_type, + access_id=gist.gist_access_id, + description=gist.gist_description, + url=gist.gist_url(), + expires=gist.gist_expires, + created_on=gist.created_on, + ) + return data + + def __json__(self): + data = dict( + ) + data.update(self.get_api_data()) + return data + ## SCM functions + + @property + def scm_instance(self): + from rhodecode.lib.vcs import get_repo + base_path = self.base_path() + return get_repo(os.path.join(*map(safe_str, + [base_path, self.gist_access_id]))) + + +class DbMigrateVersion(Base, BaseModel): + __tablename__ = 'db_migrate_version' + __table_args__ = ( + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, + ) + repository_id = Column('repository_id', String(250), primary_key=True) + repository_path = Column('repository_path', Text) + version = Column('version', Integer) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py --- a/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -22,23 +22,24 @@ Upgrade operations go here. Don't create your own engine; bind migrate_engine to your metadata """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_1_5_0 #========================================================================== # USER LOGS #========================================================================== - _reset_base(migrate_engine) - from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserLog - tbl = UserLog.__table__ + + tbl = db_1_5_0.UserLog.__table__ username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) # create username column username.create(table=tbl) - _Session = Session() + _Session = meta.Session() ## after adding that column fix all usernames - users_log = _Session.query(UserLog)\ - .options(joinedload(UserLog.user))\ - .options(joinedload(UserLog.repository)).all() + users_log = _Session.query(db_1_5_0.UserLog)\ + .options(joinedload(db_1_5_0.UserLog.user))\ + .options(joinedload(db_1_5_0.UserLog.repository)).all() for entry in users_log: entry.username = entry.user.username @@ -46,8 +47,7 @@ _Session.commit() #alter username to not null - from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserLog - tbl_name = UserLog.__tablename__ + tbl_name = db_1_5_0.UserLog.__tablename__ tbl = Table(tbl_name, MetaData(bind=migrate_engine), autoload=True, autoload_with=migrate_engine) @@ -56,7 +56,73 @@ # remove nullability from revision field col.alter(nullable=False) + # issue fixups + fixups(db_1_5_0, meta.Session) + def downgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine + + +def fixups(models, _SESSION): + # ** create default permissions ** # + #===================================== + for p in models.Permission.PERMS: + if not models.Permission.get_by_key(p[0]): + new_perm = models.Permission() + new_perm.permission_name = p[0] + new_perm.permission_longname = p[0] #translation err with p[1] + print 'Creating new permission %s' % p[0] + _SESSION().add(new_perm) + + _SESSION().commit() + + # ** populate default permissions ** # + #===================================== + + user = models.User.query().filter(models.User.username == 'default').scalar() + + def _make_perm(perm): + new_perm = models.UserToPerm() + new_perm.user = user + new_perm.permission = models.Permission.get_by_key(perm) + return new_perm + + def _get_group(perm_name): + return '.'.join(perm_name.split('.')[:1]) + + perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all() + defined_perms_groups = map(_get_group, + (x.permission.permission_name for x in perms)) + log.debug('GOT ALREADY DEFINED:%s' % perms) + DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS + + # for every default permission that needs to be created, we check if + # it's group is already defined, if it's not we create default perm + for perm_name in DEFAULT_PERMS: + gr = _get_group(perm_name) + if gr not in defined_perms_groups: + log.debug('GR:%s not found, creating permission %s' + % (gr, perm_name)) + new_perm = _make_perm(perm_name) + _SESSION().add(new_perm) + _SESSION().commit() + + # ** create default options ** # + #=============================== + skip_existing = True + for k, v in [ + ('default_repo_enable_locking', False), + ('default_repo_enable_downloads', False), + ('default_repo_enable_statistics', False), + ('default_repo_private', False), + ('default_repo_type', 'hg')]: + + if skip_existing and models.RhodeCodeSetting.get_by_name(k) is not None: + log.debug('Skipping option %s' % k) + continue + setting = models.RhodeCodeSetting(k, v) + _SESSION().add(setting) + + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py --- a/rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py Wed Jul 02 19:03:13 2014 -0400 @@ -12,7 +12,7 @@ from rhodecode.model.meta import Base from rhodecode.model import meta -from rhodecode.lib.dbmigrate.versions import _reset_base +from rhodecode.lib.dbmigrate.versions import _reset_base, notify log = logging.getLogger(__name__) @@ -23,28 +23,34 @@ Don't create your own engine; bind migrate_engine to your metadata """ _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_1_5_2 #========================================================================== # USER LOGS #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_5_2 import UserIpMap - tbl = UserIpMap.__table__ + tbl = db_1_5_2.UserIpMap.__table__ tbl.create() #========================================================================== # REPOSITORIES #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_5_2 import Repository - tbl = Repository.__table__ + tbl = db_1_5_2.Repository.__table__ changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # create username column changeset_cache.create(table=tbl) - #fix cache data - repositories = Repository.getAll() - for entry in repositories: - entry.update_changeset_cache() + # issue fixups + fixups(db_1_5_2, meta.Session) def downgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Upgrading repositories Caches') + repositories = models.Repository.getAll() + for repo in repositories: + print repo + repo.update_changeset_cache() + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/011_version_1_6_0.py --- a/rhodecode/lib/dbmigrate/versions/011_version_1_6_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/versions/011_version_1_6_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -12,7 +12,7 @@ from rhodecode.model.meta import Base from rhodecode.model import meta -from rhodecode.lib.dbmigrate.versions import _reset_base +from rhodecode.lib.dbmigrate.versions import _reset_base, notify log = logging.getLogger(__name__) @@ -23,14 +23,27 @@ Don't create your own engine; bind migrate_engine to your metadata """ _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_1_6_0 + #========================================================================== # USER LOGS #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_6_0 import RepositoryField - tbl = RepositoryField.__table__ + tbl = db_1_6_0.RepositoryField.__table__ tbl.create() + # issue fixups + fixups(db_1_6_0, meta.Session) + def downgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Upgrading repositories Caches') + repositories = models.Repository.getAll() + for repo in repositories: + print repo + repo.update_changeset_cache() + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/012_version_1_7_0.py --- a/rhodecode/lib/dbmigrate/versions/012_version_1_7_0.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/versions/012_version_1_7_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -23,46 +23,128 @@ Don't create your own engine; bind migrate_engine to your metadata """ _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_1_7_0 #========================================================================== # UserUserGroupToPerm #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserUserGroupToPerm - tbl = UserUserGroupToPerm.__table__ + tbl = db_1_7_0.UserUserGroupToPerm.__table__ tbl.create() #========================================================================== # UserGroupUserGroupToPerm #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroupUserGroupToPerm - tbl = UserGroupUserGroupToPerm.__table__ + tbl = db_1_7_0.UserGroupUserGroupToPerm.__table__ tbl.create() #========================================================================== # Gist #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_7_0 import Gist - tbl = Gist.__table__ + tbl = db_1_7_0.Gist.__table__ tbl.create() #========================================================================== # UserGroup #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroup - tbl = UserGroup.__table__ - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None) + tbl = db_1_7_0.UserGroup.__table__ + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), + nullable=True, unique=False, default=None) # create username column user_id.create(table=tbl) #========================================================================== # RepoGroup #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_7_0 import RepoGroup - tbl = RepoGroup.__table__ - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None) + tbl = db_1_7_0.RepoGroup.__table__ + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), + nullable=True, unique=False, default=None) # create username column user_id.create(table=tbl) + # issue fixups + fixups(db_1_7_0, meta.Session) + + def downgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine + + +def fixups(models, _SESSION): + # ** create default permissions ** # + #===================================== + for p in models.Permission.PERMS: + if not models.Permission.get_by_key(p[0]): + new_perm = models.Permission() + new_perm.permission_name = p[0] + new_perm.permission_longname = p[0] #translation err with p[1] + _SESSION().add(new_perm) + + _SESSION().commit() + + # ** populate default permissions ** # + #===================================== + + user = models.User.query().filter(models.User.username == 'default').scalar() + + def _make_perm(perm): + new_perm = models.UserToPerm() + new_perm.user = user + new_perm.permission = models.Permission.get_by_key(perm) + return new_perm + + def _get_group(perm_name): + return '.'.join(perm_name.split('.')[:1]) + + perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all() + defined_perms_groups = map(_get_group, + (x.permission.permission_name for x in perms)) + log.debug('GOT ALREADY DEFINED:%s' % perms) + DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS + + # for every default permission that needs to be created, we check if + # it's group is already defined, if it's not we create default perm + for perm_name in DEFAULT_PERMS: + gr = _get_group(perm_name) + if gr not in defined_perms_groups: + log.debug('GR:%s not found, creating permission %s' + % (gr, perm_name)) + new_perm = _make_perm(perm_name) + _SESSION().add(new_perm) + _SESSION().commit() + + #fix all usergroups + + def _create_default_perms(user_group): + # create default permission + default_perm = 'usergroup.read' + def_user = models.User.get_default_user() + for p in def_user.user_perms: + if p.permission.permission_name.startswith('usergroup.'): + default_perm = p.permission.permission_name + break + + user_group_to_perm = models.UserUserGroupToPerm() + user_group_to_perm.permission = models.Permission.get_by_key(default_perm) + + user_group_to_perm.user_group = user_group + user_group_to_perm.user_id = def_user.user_id + return user_group_to_perm + + for ug in models.UserGroup.get_all(): + perm_obj = _create_default_perms(ug) + _SESSION().add(perm_obj) + _SESSION().commit() + + adm = models.User.get_first_admin() + # fix owners of UserGroup + for ug in _SESSION().query(models.UserGroup).all(): + ug.user_id = adm.user_id + _SESSION().add(ug) + _SESSION().commit() + + # fix owners of RepoGroup + for ug in _SESSION().query(models.RepoGroup).all(): + ug.user_id = adm.user_id + _SESSION().add(ug) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/014_version_1_7_1.py --- a/rhodecode/lib/dbmigrate/versions/014_version_1_7_1.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/versions/014_version_1_7_1.py Wed Jul 02 19:03:13 2014 -0400 @@ -23,16 +23,28 @@ Don't create your own engine; bind migrate_engine to your metadata """ _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_1_7_0 #========================================================================== # Gist #========================================================================== - from rhodecode.lib.dbmigrate.schema.db_1_7_0 import Gist - tbl = Gist.__table__ + tbl = db_1_7_0.Gist.__table__ user_id = tbl.columns.gist_expires user_id.alter(type=Float(53)) + # issue fixups + fixups(db_1_7_0, meta.Session) + def downgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine + + +def fixups(models, _SESSION): + # fix nullable columns on last_update + for r in models.Repository().get_all(): + if r.updated_on is None: + r.updated_on = datetime.datetime.fromtimestamp(0) + _SESSION().add(r) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/015_version_1_8_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/015_version_1_8_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,82 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_1_8_0 + tbl = db_1_8_0.RhodeCodeSetting.__table__ + app_settings_type = Column("app_settings_type", + String(255, convert_unicode=False, assert_unicode=None), + nullable=True, unique=None, default=None) + app_settings_type.create(table=tbl) + + # issue fixups + fixups(db_1_8_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Fixing default options now...') + + settings = [ + #general + ('realm', '', 'unicode'), + ('title', '', 'unicode'), + ('ga_code', '', 'unicode'), + ('show_public_icon', False, 'bool'), + ('show_private_icon', True, 'bool'), + ('stylify_metatags', True, 'bool'), + + # defaults + ('default_repo_enable_locking', False, 'bool'), + ('default_repo_enable_downloads', False, 'bool'), + ('default_repo_enable_statistics', False, 'bool'), + ('default_repo_private', False, 'bool'), + ('default_repo_type', 'hg', 'unicode'), + + #other + ('dashboard_items', 100, 'int'), + ('show_version', True, 'bool') + ] + + for name, default, type_ in settings: + setting = models.RhodeCodeSetting.get_by_name(name) + if not setting: + # if we don't have this option create it + setting = models.RhodeCodeSetting(name, default, type_) + + # fix certain key to new defaults + if name in ['title', 'show_public_icon']: + # change title if it's only the default + if name == 'title' and setting.app_settings_value == 'RhodeCode': + setting.app_settings_value = default + else: + setting.app_settings_value = default + + setting._app_settings_type = type_ + _SESSION().add(setting) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/016_version_2_0_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/016_version_2_0_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,69 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_0_0 + tbl = db_2_0_0.User.__table__ + + extern_type = Column("extern_type", + String(255, convert_unicode=False, assert_unicode=None), + nullable=True, unique=None, default=None) + extern_type.create(table=tbl) + + extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), + nullable=True, unique=None, default=None) + extern_name.create(table=tbl) + + created_on = Column('created_on', DateTime(timezone=False), + nullable=True, default=datetime.datetime.now) + created_on.create(table=tbl) + + # issue fixups + fixups(db_2_0_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Fixing default created on') + + for usr in models.User.get_all(): + usr.created_on = datetime.datetime.now() + _SESSION().add(usr) + _SESSION().commit() + + notify('Migrating LDAP attribute to extern') + for usr in models.User.get_all(): + ldap_dn = usr.ldap_dn + if ldap_dn: + usr.extern_name = ldap_dn + usr.extern_type = 'ldap' + else: + usr.extern_name = 'rhodecode' + usr.extern_type = 'rhodecode' + _SESSION().add(usr) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/017_version_2_0_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/017_version_2_0_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,53 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_0_0 + tbl = db_2_0_0.UserGroup.__table__ + + user_group_description = Column("user_group_description", + String(10000, convert_unicode=False, + assert_unicode=None), nullable=True, + unique=None, default=None) + user_group_description.create(table=tbl) + + created_on = Column('created_on', DateTime(timezone=False), + nullable=True, default=datetime.datetime.now) + created_on.create(table=tbl) + + # issue fixups + fixups(db_2_0_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Fixing default created on') + + for gr in models.UserGroup.get_all(): + gr.created_on = datetime.datetime.now() + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/018_version_2_0_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/018_version_2_0_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,78 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_0_0 + + # issue fixups + fixups(db_2_0_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Fixing default auth modules') + plugins = 'rhodecode.lib.auth_modules.auth_rhodecode' + opts = [] + ldap_enabled = str2bool(getattr( + models.RhodeCodeSetting.get_by_name('ldap_active'), + 'app_settings_value', False)) + if ldap_enabled: + plugins += ',rhodecode.lib.auth_modules.auth_ldap' + opts.append(('auth_ldap_enabled', 'True', 'bool')) + + opts.append(('auth_plugins', plugins, 'list'),) + opts.append(('auth_rhodecode_enabled', 'True', 'bool')) + + for name, default, type_ in opts: + setting = models.RhodeCodeSetting.get_by_name(name) + if not setting: + # if we don't have this option create it + setting = models.RhodeCodeSetting(name, default, type_) + + _SESSION().add(setting) + _SESSION().commit() + + #copy over the LDAP settings + old_ldap = [('ldap_active', 'false', 'bool'), ('ldap_host', '', 'unicode'), + ('ldap_port', '389', 'int'), ('ldap_tls_kind', 'PLAIN', 'unicode'), + ('ldap_tls_reqcert', '', 'unicode'), ('ldap_dn_user', '', 'unicode'), + ('ldap_dn_pass', '', 'unicode'), ('ldap_base_dn', '', 'unicode'), + ('ldap_filter', '', 'unicode'), ('ldap_search_scope', '', 'unicode'), + ('ldap_attr_login', '', 'unicode'), ('ldap_attr_firstname', '', 'unicode'), + ('ldap_attr_lastname', '', 'unicode'), ('ldap_attr_email', '', 'unicode')] + for k, v, t in old_ldap: + old_setting = models.RhodeCodeSetting.get_by_name(k) + name = 'auth_%s' % k + setting = models.RhodeCodeSetting.get_by_name(name) + if not setting: + # if we don't have this option create it + setting = models.RhodeCodeSetting(name, old_setting.app_settings_value, t) + + _SESSION().add(setting) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/019_version_2_0_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/019_version_2_0_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,42 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_0_0 + tbl = db_2_0_0.RhodeCodeSetting.__table__ + settings_value = tbl.columns.app_settings_value + settings_value.alter(type=String(4096)) + + # issue fixups + fixups(db_2_0_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + return diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/020_version_2_0_1.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/020_version_2_0_1.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,45 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_0_1 + + # issue fixups + fixups(db_2_0_1, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + #fix all empty extern type users to default 'rhodecode' + for usr in models.User.query().all(): + if not usr.extern_name: + usr.extern_name = 'rhodecode' + usr.extern_type = 'rhodecode' + _SESSION().add(usr) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/021_version_2_0_2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/021_version_2_0_2.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,76 @@ +import os +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_0_1 + tbl = db_2_0_1.RepoGroup.__table__ + + created_on = Column('created_on', DateTime(timezone=False), nullable=True, + default=datetime.datetime.now) + created_on.create(table=tbl) + + #fix null values on certain columns when upgrading from older releases + tbl = db_2_0_1.UserLog.__table__ + col = tbl.columns.user_id + col.alter(nullable=True) + + tbl = db_2_0_1.UserFollowing.__table__ + col = tbl.columns.follows_repository_id + col.alter(nullable=True) + + tbl = db_2_0_1.UserFollowing.__table__ + col = tbl.columns.follows_user_id + col.alter(nullable=True) + + # issue fixups + fixups(db_2_0_1, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Fixing default created on for repo groups') + + for gr in models.RepoGroup.get_all(): + gr.created_on = datetime.datetime.now() + _SESSION().add(gr) + _SESSION().commit() + + repo_store_path = models.RhodeCodeUi.get_repos_location() + _store = os.path.join(repo_store_path, '.cache', 'largefiles') + notify('Setting largefiles usercache') + print _store + + if not models.RhodeCodeUi.get_by_key('usercache'): + largefiles = models.RhodeCodeUi() + largefiles.ui_section = 'largefiles' + largefiles.ui_key = 'usercache' + largefiles.ui_value = _store + _SESSION().add(largefiles) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/022_version_2_0_2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/022_version_2_0_2.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,70 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_0_2 + + # issue fixups + fixups(db_2_0_2, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('fixing new schema for landing_rev') + + for repo in models.Repository.get_all(): + print u'repo %s old landing rev is: %s' % (repo, repo.landing_rev) + _rev = repo.landing_rev[1] + _rev_type = 'rev' # default + + if _rev in ['default', 'master']: + _rev_type = 'branch' + elif _rev in ['tip']: + _rev_type = 'rev' + else: + try: + scm = repo.scm_instance + if scm: + known_branches = scm.branches.keys() + known_bookmarks = scm.bookmarks.keys() + if _rev in known_branches: + _rev_type = 'branch' + elif _rev in known_bookmarks: + _rev_type = 'book' + except Exception, e: + print e + print 'continue...' + #we don't want any error to break the process + pass + + _new_landing_rev = '%s:%s' % (_rev_type, _rev) + print u'setting to %s' % _new_landing_rev + repo.landing_rev = _new_landing_rev + _SESSION().add(repo) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/023_version_2_1_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/023_version_2_1_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,42 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_1_0 + + tbl = db_2_1_0.UserApiKeys.__table__ + tbl.create() + + # issue fixups + fixups(db_2_1_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + pass diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/024_version_2_1_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/024_version_2_1_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,67 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_1_0 + + # issue fixups + fixups(db_2_1_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + from pylons import config + from rhodecode.lib.utils2 import str2bool + + notify('migrating options from .ini file') + use_gravatar = str2bool(config.get('use_gravatar')) + print('Setting gravatar use to: %s' % use_gravatar) + sett = models.RhodeCodeSetting.create_or_update('use_gravatar', + use_gravatar, 'bool') + _SESSION().add(sett) + _SESSION.commit() + #set the new format of gravatar URL + gravatar_url = models.User.DEFAULT_GRAVATAR_URL + if config.get('alternative_gravatar_url'): + gravatar_url = config.get('alternative_gravatar_url') + + print('Setting gravatar url to:%s' % gravatar_url) + sett = models.RhodeCodeSetting.create_or_update('gravatar_url', + gravatar_url, 'unicode') + _SESSION().add(sett) + _SESSION.commit() + + #now create new changed value of clone_url + clone_uri_tmpl = models.Repository.DEFAULT_CLONE_URI + print('settings new clone url template to %s' % clone_uri_tmpl) + + sett = models.RhodeCodeSetting.create_or_update('clone_uri_tmpl', + clone_uri_tmpl, 'unicode') + _SESSION().add(sett) + _SESSION.commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/025_version_2_1_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/025_version_2_1_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,43 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_1_0 + + # issue fixups + fixups(db_2_1_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Creating upgrade URL') + sett = models.RhodeCodeSetting.create_or_update('update_url', + models.RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode') + _SESSION().add(sett) + _SESSION.commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/026_version_2_2_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/026_version_2_2_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,44 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_2_0 + + tbl = db_2_2_0.User.__table__ + + user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data + user_data.create(table=tbl) + + # issue fixups + fixups(db_2_2_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + pass diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/027_version_2_2_0.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/027_version_2_2_0.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,59 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_2_0 + + # issue fixups + fixups(db_2_2_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + # ** create default permissions ** # + #===================================== + for p in models.Permission.PERMS: + if not models.Permission.get_by_key(p[0]): + new_perm = models.Permission() + new_perm.permission_name = p[0] + new_perm.permission_longname = p[0] #translation err with p[1] + print 'Creating new permission %s' % p[0] + _SESSION().add(new_perm) + + _SESSION().commit() + + # ** set default create_on_write to active + user = models.User.get_default_user() + _def = 'hg.create.write_on_repogroup.true' + new = models.UserToPerm() + new.user = user + new.permission = models.Permission.get_by_key(_def) + print 'Setting default to %s' % _def + _SESSION().add(new) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/028_version_2_2_3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/028_version_2_2_3.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,44 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_2_0 + + tbl = db_2_2_0.UserGroup.__table__ + + user_data = Column("group_data", LargeBinary(), nullable=True) # JSON data + user_data.create(table=tbl) + + # issue fixups + fixups(db_2_2_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + pass diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/029_version_2_2_3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/029_version_2_2_3.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,52 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_2_0 + + # issue fixups + fixups(db_2_2_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Adding grid items options now...') + + settings = [ + ('admin_grid_items', 25, 'int'), # old hardcoded value was 25 + ] + + for name, default, type_ in settings: + setting = models.RhodeCodeSetting.get_by_name(name) + if not setting: + # if we don't have this option create it + setting = models.RhodeCodeSetting(name, default, type_) + setting._app_settings_type = type_ + _SESSION().add(setting) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/030_version_2_2_3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/030_version_2_2_3.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,44 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_2_0 + + tbl = db_2_2_0.Repository.__table__ + + repo_state = Column("repo_state", String(255), nullable=True) + repo_state.create(table=tbl) + + # issue fixups + fixups(db_2_2_0, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + pass diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/031_version_2_2_3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/dbmigrate/versions/031_version_2_2_3.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,45 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper, joinedload +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * +from rhodecode.lib.utils2 import str2bool + +from rhodecode.model.meta import Base +from rhodecode.model import meta +from rhodecode.lib.dbmigrate.versions import _reset_base, notify + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + _reset_base(migrate_engine) + from rhodecode.lib.dbmigrate.schema import db_2_2_3 + + # issue fixups + fixups(db_2_2_3, meta.Session) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + +def fixups(models, _SESSION): + notify('Creating repository states') + for repo in models.Repository.get_all(): + _state = models.Repository.STATE_CREATED + print 'setting repo %s state to "%s"' % (repo, _state) + repo.repo_state = _state + _SESSION().add(repo) + _SESSION().commit() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/dbmigrate/versions/__init__.py --- a/rhodecode/lib/dbmigrate/versions/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/dbmigrate/versions/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.dbmigrate.versions.__init__ - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Package containing new versions of database models - - :created_on: Dec 11, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,23 +11,50 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.dbmigrate.versions.__init__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Package containing new versions of database models + +:created_on: Dec 11, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from sqlalchemy import * from sqlalchemy.exc import DatabaseError from sqlalchemy.orm import relation, backref, class_mapper, joinedload -from sqlalchemy.orm.session import Session from sqlalchemy.ext.declarative import declarative_base - +from sqlalchemy.orm import scoped_session, sessionmaker from rhodecode.lib.dbmigrate.migrate import * from rhodecode.lib.dbmigrate.migrate.changeset import * -from rhodecode.model.meta import Base from rhodecode.model import meta +def notify(msg, caps=True): + """ + Notification for migrations messages + """ + ml = len(msg) + (4 * 2) + formatted_msg = ('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)) + if caps: + formatted_msg = formatted_msg.upper() + print(formatted_msg) + + def _reset_base(migrate_engine): ## RESET COMPLETLY THE metadata for sqlalchemy to use previous declared Base Base = declarative_base() Base.metadata.clear() Base.metadata = MetaData() Base.metadata.bind = migrate_engine + + # new session and base + #meta.Session = scoped_session(sessionmaker(expire_on_commit=True,)) + #meta.Session.configure(bind=migrate_engine) meta.Base = Base + + notify('SQLA BASE RESET !') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/diffs.py --- a/rhodecode/lib/diffs.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/diffs.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,17 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.diffs - ~~~~~~~~~~~~~~~~~~~ - - Set of diffing helpers, previously part of vcs - - - :created_on: Dec 4, 2011 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :original copyright: 2007-2008 by Armin Ronacher - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -24,7 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.diffs +~~~~~~~~~~~~~~~~~~~ +Set of diffing helpers, previously part of vcs + + +:created_on: Dec 4, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import re import difflib import logging @@ -438,7 +436,9 @@ chunks = [] if op == 'D' and chunks: - chunks = [] + # if not full diff mode show deleted file contents + if self.diff_limit is not None: + chunks = [] chunks.insert(0, [{ 'old_lineno': '', diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/exceptions.py --- a/rhodecode/lib/exceptions.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/exceptions.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.exceptions - ~~~~~~~~~~~~~~~~~~~~~~~~ - - Set of custom exceptions used in RhodeCode - - :created_on: Nov 17, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.exceptions +~~~~~~~~~~~~~~~~~~~~~~~~ + +Set of custom exceptions used in RhodeCode + +:created_on: Nov 17, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" from webob.exc import HTTPClientError diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/graphmod.py --- a/rhodecode/lib/graphmod.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/graphmod.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,3 +1,16 @@ +# -*- 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 . """ Modified mercurial DAG graph functions that re-uses VCS structure @@ -36,7 +49,7 @@ repo = repo elif alias == 'git': def cl(rev): - return [x.revision for x in repo[rev].parents()] + return [x.revision for x in repo[rev].parents] repo = repo lowestrev = min(revs) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/helpers.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,4 +1,18 @@ -"""Helper functions +# -*- 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 . +""" +Helper functions Consists of functions to typically be used within templates, but also available to Controllers. This module is available to both as 'h'. @@ -365,8 +379,8 @@ """ def __init__(self, category, message): - self.category=category - self.message=message + self.category = category + self.message = message def __str__(self): return self.message @@ -475,6 +489,10 @@ # attr to return from fetched user person_getter = lambda usr: getattr(usr, show_attr) + # if author is already an instance use it for extraction + if isinstance(author, User): + return person_getter(author) + # Valid email in the attribute passed, see if they're in the system _email = email(author) if _email != '': @@ -512,6 +530,9 @@ :param value: """ + if not value: + return '' + value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', '
see => \\1
', value) value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', @@ -534,11 +555,9 @@ """ if value: - return HTML.tag('img', src=url("/images/icons/accept.png"), - alt=_('True')) + return HTML.tag('i', class_="icon-ok-sign") else: - return HTML.tag('img', src=url("/images/icons/cancel.png"), - alt=_('False')) + return HTML.tag('i', class_="icon-minus-sign") def action_parser(user_log, feed=False, parse_cs=False): @@ -722,51 +741,51 @@ # action : translated str, callback(extractor), icon action_map = { 'user_deleted_repo': (_('[deleted] repository'), - None, 'database_delete.png'), + None, 'icon-trash'), 'user_created_repo': (_('[created] repository'), - None, 'database_add.png'), + None, 'icon-plus icon-plus-colored'), 'user_created_fork': (_('[created] repository as fork'), - None, 'arrow_divide.png'), + None, 'icon-code-fork'), 'user_forked_repo': (_('[forked] repository'), - get_fork_name, 'arrow_divide.png'), + get_fork_name, 'icon-code-fork'), 'user_updated_repo': (_('[updated] repository'), - None, 'database_edit.png'), + None, 'icon-pencil icon-pencil-colored'), 'user_downloaded_archive': (_('[downloaded] archive from repository'), - get_archive_name, 'page_white_compressed.png'), + get_archive_name, 'icon-download-alt'), 'admin_deleted_repo': (_('[delete] repository'), - None, 'database_delete.png'), + None, 'icon-trash'), 'admin_created_repo': (_('[created] repository'), - None, 'database_add.png'), + None, 'icon-plus icon-plus-colored'), 'admin_forked_repo': (_('[forked] repository'), - None, 'arrow_divide.png'), + None, 'icon-code-fork icon-fork-colored'), 'admin_updated_repo': (_('[updated] repository'), - None, 'database_edit.png'), + None, 'icon-pencil icon-pencil-colored'), 'admin_created_user': (_('[created] user'), - get_user_name, 'user_add.png'), + get_user_name, 'icon-user icon-user-colored'), 'admin_updated_user': (_('[updated] user'), - get_user_name, 'user_edit.png'), + get_user_name, 'icon-user icon-user-colored'), 'admin_created_users_group': (_('[created] user group'), - get_users_group, 'group_add.png'), + get_users_group, 'icon-pencil icon-pencil-colored'), 'admin_updated_users_group': (_('[updated] user group'), - get_users_group, 'group_edit.png'), + get_users_group, 'icon-pencil icon-pencil-colored'), 'user_commented_revision': (_('[commented] on revision in repository'), - get_cs_links, 'comment_add.png'), + get_cs_links, 'icon-comment icon-comment-colored'), 'user_commented_pull_request': (_('[commented] on pull request for'), - get_pull_request, 'comment_add.png'), + get_pull_request, 'icon-comment icon-comment-colored'), 'user_closed_pull_request': (_('[closed] pull request for'), - get_pull_request, 'tick.png'), + get_pull_request, 'icon-check'), 'push': (_('[pushed] into'), - get_cs_links, 'script_add.png'), + get_cs_links, 'icon-arrow-up'), 'push_local': (_('[committed via RhodeCode] into repository'), - get_cs_links, 'script_edit.png'), + get_cs_links, 'icon-pencil icon-pencil-colored'), 'push_remote': (_('[pulled from remote] into repository'), - get_cs_links, 'connect.png'), + get_cs_links, 'icon-arrow-up'), 'pull': (_('[pulled] from'), - None, 'down_16.png'), + None, 'icon-arrow-down'), 'started_following_repo': (_('[started following] repository'), - None, 'heart_add.png'), + None, 'icon-heart icon-heart-colored'), 'stopped_following_repo': (_('[stopped following] repository'), - None, 'heart_delete.png'), + None, 'icon-heart-empty icon-heart-colored'), } action_str = action_map.get(action, action) @@ -790,9 +809,9 @@ if len(x) > 1: action, action_params = x - tmpl = """%s""" + tmpl = """""" ico = action_map.get(action, ['', '', ''])[2] - return literal(tmpl % ((url('/images/icons/')), ico, action)) + return literal(tmpl % (ico, action)) # returned callbacks we need to call to get return [lambda: literal(action), action_params_func, action_parser_icon] @@ -803,8 +822,8 @@ # PERMS #============================================================================== from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \ -HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \ -HasReposGroupPermissionAny +HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \ +HasRepoGroupPermissionAny #============================================================================== @@ -812,42 +831,36 @@ #============================================================================== def gravatar_url(email_address, size=30, ssl_enabled=True): - from pylons import url # doh, we need to re-import url to mock it later - from rhodecode import CONFIG + # doh, we need to re-import those to mock it later + from pylons import url + from pylons import tmpl_context as c _def = 'anonymous@rhodecode.org' # default gravatar - use_gravatar = str2bool(CONFIG.get('use_gravatar')) - alternative_gravatar_url = CONFIG.get('alternative_gravatar_url', '') + _use_gravatar = c.visual.use_gravatar + _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL + email_address = email_address or _def - if not use_gravatar or not email_address or email_address == _def: + if isinstance(email_address, unicode): + #hashlib crashes on unicode items + email_address = safe_str(email_address) + + if not _use_gravatar or not email_address or email_address == _def: + # pick best matching size to one given in size param f = lambda a, l: min(l, key=lambda x: abs(x - a)) return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30])) - if use_gravatar and alternative_gravatar_url: - tmpl = alternative_gravatar_url + if _use_gravatar: + _md5 = lambda s: hashlib.md5(s).hexdigest() + + tmpl = _gravatar_url parsed_url = urlparse.urlparse(url.current(qualified=True)) tmpl = tmpl.replace('{email}', email_address)\ - .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \ + .replace('{md5email}', _md5(email_address.lower())) \ .replace('{netloc}', parsed_url.netloc)\ .replace('{scheme}', parsed_url.scheme)\ - .replace('{size}', str(size)) + .replace('{size}', safe_str(size)) return tmpl - default = 'identicon' - baseurl_nossl = "http://www.gravatar.com/avatar/" - baseurl_ssl = "https://secure.gravatar.com/avatar/" - baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl - - if isinstance(email_address, unicode): - #hashlib crashes on unicode items - email_address = safe_str(email_address) - # construct the url - gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?" - gravatar_url += urllib.urlencode({'d': default, 's': str(size)}) - - return gravatar_url - - class Page(_Page): """ Custom pager to match rendering style with YUI paginator @@ -934,7 +947,9 @@ nav_items.append(self._pagerlink(self.last_page, self.last_page)) ## prerender links - nav_items.append(literal('' % str(int(self.page)+1))) + #_page_link = url.current() + #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) + #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) return self.separator.join(nav_items) def pager(self, format='~2~', page_param='page', partial_param='partial', diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/hooks.py --- a/rhodecode/lib/hooks.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/hooks.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.hooks - ~~~~~~~~~~~~~~~~~~~ - - Hooks runned by rhodecode - - :created_on: Aug 6, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,18 +11,27 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.hooks +~~~~~~~~~~~~~~~~~~~ + +Hooks runned by rhodecode + +:created_on: Aug 6, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os import sys import time import binascii -import traceback -from inspect import isfunction from rhodecode.lib.vcs.utils.hgcompat import nullrev, revrange from rhodecode.lib import helpers as h from rhodecode.lib.utils import action_logger from rhodecode.lib.vcs.backends.base import EmptyChangeset -from rhodecode.lib.compat import json from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError from rhodecode.lib.utils2 import safe_str, _extract_extras from rhodecode.model.db import Repository, User @@ -136,7 +134,7 @@ # extension hook call from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'PULL_HOOK', None) - if isfunction(callback): + if callable(callback): kw = {} kw.update(ex) callback(**kw) @@ -165,8 +163,8 @@ ex = _extract_extras() - action = ex.action + ':%s' - + action_tmpl = ex.action + ':%s' + revs = [] if ex.scm == 'hg': node = kwargs['node'] @@ -176,26 +174,25 @@ if len(revs) == 0: return (nullrev, nullrev) - return (max(revs), min(revs)) + return max(revs), min(revs) else: - return (len(repo) - 1, 0) + return len(repo) - 1, 0 stop, start = get_revs(repo, [node + ':']) - h = binascii.hexlify - revs = [h(repo[r].node()) for r in xrange(start, stop + 1)] + _h = binascii.hexlify + revs = [_h(repo[r].node()) for r in xrange(start, stop + 1)] elif ex.scm == 'git': revs = kwargs.get('_git_revs', []) if '_git_revs' in kwargs: kwargs.pop('_git_revs') - action = action % ','.join(revs) - + action = action_tmpl % ','.join(revs) action_logger(ex.username, action, ex.repository, ex.ip, commit=True) # extension hook call from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'PUSH_HOOK', None) - if isfunction(callback): + if callable(callback): kw = {'pushed_revs': revs} kw.update(ex) callback(**kw) @@ -242,7 +239,7 @@ """ from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None) - if isfunction(callback): + if callable(callback): kw = {} kw.update(repository_dict) kw.update({'created_by': created_by}) @@ -253,13 +250,38 @@ def check_allowed_create_user(user_dict, created_by, **kwargs): + # pre create hooks from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'PRE_CREATE_USER_HOOK', None) - if isfunction(callback): + if callable(callback): allowed, reason = callback(created_by=created_by, **user_dict) if not allowed: raise UserCreationError(reason) + # license limit hook + import rhodecode + from rhodecode.model.license import LicenseModel + license_token = rhodecode.CONFIG.get('license_token') + license_key = LicenseModel.get_license_key() + license_info = LicenseModel.get_license_info( + license_token=license_token, enc_license_key=license_key, + fill_defaults=True) + expiration_check = False + if expiration_check: + now = time.time() + #check expiration + if now > license_info['valid_till']: + reason = ('Your license has expired, ' + 'please contact support to extend your license.') + raise UserCreationError(reason) + # user count check + cur_user_count = User.query().count() + if cur_user_count > int(license_info['users']) > 0: + reason = ('You have reached the maximum number of users (%s), ' + 'please contact support to extend your license.' + % license_info['users']) + raise UserCreationError(reason) + def log_create_user(user_dict, created_by, **kwargs): """ @@ -294,7 +316,7 @@ """ from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'CREATE_USER_HOOK', None) - if isfunction(callback): + if callable(callback): return callback(created_by=created_by, **user_dict) return 0 @@ -327,7 +349,7 @@ """ from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None) - if isfunction(callback): + if callable(callback): kw = {} kw.update(repository_dict) kw.update({'deleted_by': deleted_by, @@ -371,7 +393,7 @@ """ from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'DELETE_USER_HOOK', None) - if isfunction(callback): + if callable(callback): return callback(deleted_by=deleted_by, **user_dict) return 0 @@ -404,7 +426,8 @@ path, ini_name = os.path.split(extras['config']) conf = appconfig('config:%s' % ini_name, relative_to=path) - load_environment(conf.global_conf, conf.local_conf) + load_environment(conf.global_conf, conf.local_conf, test_env=False, + test_index=False) engine = engine_from_config(conf, 'sqlalchemy.db1.') init_model(engine) @@ -432,7 +455,6 @@ # if push hook is enabled via web interface elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH): - rev_data = [] for l in revs: old_rev, new_rev, ref = l.split(' ') @@ -445,10 +467,16 @@ 'name': _ref_data[2].strip()}) git_revs = [] - for push_ref in rev_data: + + for push_ref in rev_data: _type = push_ref['type'] if _type == 'heads': if push_ref['old_rev'] == EmptyChangeset().raw_id: + # update the symbolic ref if we push new repo + if repo.is_empty(): + repo._repo.refs.set_symbolic_ref('HEAD', + 'refs/heads/%s' % push_ref['name']) + cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'" heads = repo.run_git_command(cmd)[0] heads = heads.replace(push_ref['ref'], '') diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/indexers/__init__.py --- a/rhodecode/lib/indexers/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/indexers/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.indexers.__init__ - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Whoosh indexing module for RhodeCode - - :created_on: Aug 17, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.indexers.__init__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whoosh indexing module for RhodeCode + +:created_on: Aug 17, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os import sys import logging @@ -144,7 +145,7 @@ res.update({'content_short': content_short, 'content_short_hl': self.highlight(content_short), 'f_path': f_path - }) + }) elif self.search_type == 'path': full_repo_path = jn(self.repo_location, res['repository']) f_path = res['path'].split(full_repo_path)[-1] diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/indexers/daemon.py --- a/rhodecode/lib/indexers/daemon.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/indexers/daemon.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.indexers.daemon - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - A daemon will read from task table and run tasks - - :created_on: Jan 26, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.indexers.daemon +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A daemon will read from task table and run tasks + +:created_on: Jan 26, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os @@ -111,14 +112,16 @@ self.initial = False def _get_index_revision(self, repo): - db_repo = Repository.get_by_repo_name(repo.name) + db_repo = Repository.get_by_repo_name(repo.name_unicode) landing_rev = 'tip' if db_repo: - landing_rev = db_repo.landing_rev + _rev_type, _rev = db_repo.landing_rev + landing_rev = _rev return landing_rev - def _get_index_changeset(self, repo): - index_rev = self._get_index_revision(repo) + def _get_index_changeset(self, repo, index_rev=None): + if not index_rev: + index_rev = self._get_index_revision(repo) cs = repo.get_changeset(index_rev) return cs @@ -139,7 +142,7 @@ pass return index_paths_ - def get_node(self, repo, path): + def get_node(self, repo, path, index_rev=None): """ gets a filenode based on given full path.It operates on string for hg git compatability. @@ -150,20 +153,20 @@ """ root_path = safe_str(repo.path)+'/' parts = safe_str(path).partition(root_path) - cs = self._get_index_changeset(repo) + cs = self._get_index_changeset(repo, index_rev=index_rev) node = cs.get_node(parts[-1]) return node def get_node_mtime(self, node): return mktime(node.last_changeset.date.timetuple()) - def add_doc(self, writer, path, repo, repo_name): + def add_doc(self, writer, path, repo, repo_name, index_rev=None): """ Adding doc to writer this function itself fetches data from the instance of vcs backend """ - node = self.get_node(repo, path) + node = self.get_node(repo, path, index_rev) indexed = indexed_w_content = 0 # we just index the content of chosen files, and skip binary files if node.extension in INDEX_EXTENSIONS and not node.is_binary: @@ -249,8 +252,9 @@ i_cnt = iwc_cnt = 0 log.debug('building index for %s @revision:%s' % (repo.path, self._get_index_revision(repo))) + index_rev = self._get_index_revision(repo) for idx_path in self.get_paths(repo): - i, iwc = self.add_doc(file_idx_writer, idx_path, repo, repo_name) + i, iwc = self.add_doc(file_idx_writer, idx_path, repo, repo_name, index_rev) i_cnt += i iwc_cnt += iwc diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/markup_renderer.py --- a/rhodecode/lib/markup_renderer.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/markup_renderer.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.markup_renderer - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - Renderer for markup languages with ability to parse using rst or markdown - - :created_on: Oct 27, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.markup_renderer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Renderer for markup languages with ability to parse using rst or markdown + +:created_on: Oct 27, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import re import logging @@ -120,9 +120,11 @@ return readme_data @classmethod - def plain(cls, source): + def plain(cls, source, universal_newline=True): source = safe_unicode(source) - + if universal_newline: + newline = '\n' + source = newline.join(source.splitlines()) def urlify_text(text): url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]' '|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)') @@ -150,7 +152,8 @@ except Exception: log.error(traceback.format_exc()) if safe: - return source + log.debug('Fallbacking to render in plain mode') + return cls.plain(source) else: raise @@ -180,7 +183,8 @@ except Exception: log.error(traceback.format_exc()) if safe: - return source + log.debug('Fallbacking to render in plain mode') + return cls.plain(source) else: raise diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/__init__.py --- a/rhodecode/lib/middleware/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,13 @@ +# -*- 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 . diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/errormator.py --- a/rhodecode/lib/middleware/errormator.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/errormator.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.middleware.errormator - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - middleware to handle errormator publishing of errors - - :created_on: October 18, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.middleware.errormator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +middleware to handle errormator publishing of errors + +:created_on: October 18, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + try: from errormator_client import make_errormator_middleware diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/https_fixup.py --- a/rhodecode/lib/middleware/https_fixup.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/https_fixup.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.middleware.https_fixup - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - middleware to handle https correctly - - :created_on: May 23, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.middleware.https_fixup +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +middleware to handle https correctly + +:created_on: May 23, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from rhodecode.lib.utils2 import str2bool diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/pygrack.py --- a/rhodecode/lib/middleware/pygrack.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/pygrack.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,7 +1,6 @@ import os import socket import logging -import subprocess import traceback from webob import Request, Response, exc @@ -86,7 +85,7 @@ try: out = subprocessio.SubprocessIOChunker( r'%s %s --stateless-rpc --advertise-refs "%s"' % ( - _git_path, git_command[4:], self.content_path), + _git_path, git_command[4:], self.content_path), starting_values=[ packet_len + server_advert + '0000' ] @@ -107,6 +106,7 @@ returns an iterator obj with contents of git command's response to stdout """ + _git_path = rhodecode.CONFIG.get('git_path', 'git') git_command = self._get_fixedpath(request.path_info) if git_command not in self.commands: log.debug('command %s not allowed' % git_command) @@ -124,10 +124,10 @@ gitenv['GIT_CONFIG_NOGLOBAL'] = '1' opts = dict( env=gitenv, - cwd=os.getcwd() + cwd=self.content_path, ) - cmd = r'git %s --stateless-rpc "%s"' % (git_command[4:], - self.content_path), + cmd = r'%s %s --stateless-rpc "%s"' % (_git_path, git_command[4:], + self.content_path), log.debug('handling cmd %s' % cmd) out = subprocessio.SubprocessIOChunker( cmd, @@ -141,11 +141,11 @@ if git_command in [u'git-receive-pack']: # updating refs manually after each push. # Needed for pre-1.7.0.4 git clients using regular HTTP mode. - _git_path = rhodecode.CONFIG.get('git_path', 'git') - cmd = (u'%s --git-dir "%s" ' - 'update-server-info' % (_git_path, self.content_path)) - log.debug('handling cmd %s' % cmd) - subprocess.call(cmd, shell=True) + from rhodecode.lib.vcs import get_repo + from dulwich.server import update_server_info + repo = get_repo(self.content_path) + if repo: + update_server_info(repo._repo) resp = Response() resp.content_type = 'application/x-%s-result' % git_command.encode('utf8') @@ -197,4 +197,6 @@ def make_wsgi_app(repo_name, repo_root, extras): - return GitDirectory(repo_root, repo_name, extras) + from dulwich.web import LimitedInputFilter, GunzipFilter + app = GitDirectory(repo_root, repo_name, extras) + return GunzipFilter(LimitedInputFilter(app)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/sentry.py --- a/rhodecode/lib/middleware/sentry.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/sentry.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.middleware.sentry - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - middleware to handle sentry/raven publishing of errors - - :created_on: September 18, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.middleware.sentry +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +middleware to handle sentry/raven publishing of errors + +:created_on: September 18, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + try: from raven.base import Client diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/simplegit.py --- a/rhodecode/lib/middleware/simplegit.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/simplegit.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.middleware.simplegit - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - SimpleGit middleware for handling git protocol request (push/clone etc.) - It's implemented with basic auth function - - :created_on: Apr 28, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,68 +11,38 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.middleware.simplegit +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SimpleGit middleware for handling git protocol request (push/clone etc.) +It's implemented with basic auth function + +:created_on: Apr 28, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + import os import re import logging import traceback -from dulwich import server as dulserver -from dulwich.web import LimitedInputFilter, GunzipFilter -from rhodecode.lib.exceptions import HTTPLockedRC -from rhodecode.lib.hooks import pre_pull - - -class SimpleGitUploadPackHandler(dulserver.UploadPackHandler): - - def handle(self): - write = lambda x: self.proto.write_sideband(1, x) - - graph_walker = dulserver.ProtocolGraphWalker(self, - self.repo.object_store, - self.repo.get_peeled) - objects_iter = self.repo.fetch_objects( - graph_walker.determine_wants, graph_walker, self.progress, - get_tagged=self.get_tagged) - - # Did the process short-circuit (e.g. in a stateless RPC call)? Note - # that the client still expects a 0-object pack in most cases. - if objects_iter is None: - return - - self.progress("counting objects: %d, done.\n" % len(objects_iter)) - dulserver.write_pack_objects(dulserver.ProtocolFile(None, write), - objects_iter) - messages = ['thank you for using rhodecode'] - - for msg in messages: - self.progress(msg + "\n") - # we are done - self.proto.write("0000") - - -dulserver.DEFAULT_HANDLERS = { - #git-ls-remote, git-clone, git-fetch and git-pull - 'git-upload-pack': SimpleGitUploadPackHandler, - #git-push - 'git-receive-pack': dulserver.ReceivePackHandler, -} - -# not used for now until dulwich gets fixed -#from dulwich.repo import Repo -#from dulwich.web import make_wsgi_chain - from paste.httpheaders import REMOTE_USER, AUTH_TYPE from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ - HTTPBadRequest, HTTPNotAcceptable + HTTPNotAcceptable +from rhodecode.model.db import User, RhodeCodeUi from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\ _set_extras from rhodecode.lib.base import BaseVCSController -from rhodecode.lib.auth import get_container_username -from rhodecode.lib.utils import is_valid_repo, make_ui -from rhodecode.lib.compat import json -from rhodecode.model.db import User, RhodeCodeUi +from rhodecode.lib.utils import make_ui, is_valid_repo +from rhodecode.lib.exceptions import HTTPLockedRC +from rhodecode.lib.hooks import pre_pull +from rhodecode.lib import auth_modules log = logging.getLogger(__name__) @@ -139,23 +97,34 @@ if action in ['pull', 'push']: anonymous_user = self.__get_user('default') username = anonymous_user.username - anonymous_perm = self._check_permission(action, anonymous_user, - repo_name, ip_addr) + if anonymous_user.active: + # ONLY check permissions if the user is activated + anonymous_perm = self._check_permission(action, anonymous_user, + repo_name, ip_addr) + else: + anonymous_perm = False - if not anonymous_perm or not anonymous_user.active: + if not anonymous_user.active or not anonymous_perm: + if not anonymous_user.active: + log.debug('Anonymous access is disabled, running ' + 'authentication') + if not anonymous_perm: log.debug('Not enough credentials to access this ' 'repository as anonymous user') - if not anonymous_user.active: - log.debug('Anonymous access is disabled, running ' - 'authentication') + + username = None #============================================================== # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS #============================================================== - # Attempting to retrieve username from the container - username = get_container_username(environ, self.config) + # try to auth based on environ, container auth methods + log.debug('Running PRE-AUTH for container based authentication') + pre_auth = auth_modules.authenticate('', '', environ) + if pre_auth and pre_auth.get('username'): + username = pre_auth['username'] + log.debug('PRE-AUTH got %s as username' % username) # If not authenticated by the container, running basic auth if not username: @@ -259,7 +228,6 @@ repo_name=repo_name, extras=extras, ) - app = GunzipFilter(LimitedInputFilter(app)) return app def __get_repository(self, environ): diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/simplehg.py --- a/rhodecode/lib/middleware/simplehg.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/simplehg.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.middleware.simplehg - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - SimpleHG middleware for handling mercurial protocol request - (push/clone etc.). It's implemented with basic auth function - - :created_on: Apr 28, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,26 +11,37 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.middleware.simplehg +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SimpleHG middleware for handling mercurial protocol request +(push/clone etc.). It's implemented with basic auth function + +:created_on: Apr 28, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + import os import logging import traceback - from paste.httpheaders import REMOTE_USER, AUTH_TYPE from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ - HTTPBadRequest, HTTPNotAcceptable + HTTPNotAcceptable +from rhodecode.model.db import User from rhodecode.lib.utils2 import safe_str, fix_PATH, get_server_url,\ _set_extras from rhodecode.lib.base import BaseVCSController -from rhodecode.lib.auth import get_container_username from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections -from rhodecode.lib.compat import json from rhodecode.lib.vcs.utils.hgcompat import RepoError, hgweb_mod -from rhodecode.model.db import User from rhodecode.lib.exceptions import HTTPLockedRC - +from rhodecode.lib import auth_modules log = logging.getLogger(__name__) @@ -102,23 +101,34 @@ if action in ['pull', 'push']: anonymous_user = self.__get_user('default') username = anonymous_user.username - anonymous_perm = self._check_permission(action, anonymous_user, - repo_name, ip_addr) + if anonymous_user.active: + # ONLY check permissions if the user is activated + anonymous_perm = self._check_permission(action, anonymous_user, + repo_name, ip_addr) + else: + anonymous_perm = False - if not anonymous_perm or not anonymous_user.active: + if not anonymous_user.active or not anonymous_perm: + if not anonymous_user.active: + log.debug('Anonymous access is disabled, running ' + 'authentication') + if not anonymous_perm: log.debug('Not enough credentials to access this ' 'repository as anonymous user') - if not anonymous_user.active: - log.debug('Anonymous access is disabled, running ' - 'authentication') + + username = None #============================================================== # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS #============================================================== - # Attempting to retrieve username from the container - username = get_container_username(environ, self.config) + # try to auth based on environ, container auth methods + log.debug('Running PRE-AUTH for container based authentication') + pre_auth = auth_modules.authenticate('', '', environ) + if pre_auth and pre_auth.get('username'): + username = pre_auth['username'] + log.debug('PRE-AUTH got %s as username' % username) # If not authenticated by the container, running basic auth if not username: diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/middleware/wrapper.py --- a/rhodecode/lib/middleware/wrapper.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/middleware/wrapper.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.middleware.wrapper - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - request time mesuring app - - :created_on: May 23, 2013 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.middleware.wrapper +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +request time mesuring app + +:created_on: May 23, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import time import logging from rhodecode.lib.base import _get_ip_addr, _get_access_path diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/__init__.py --- a/rhodecode/lib/paster_commands/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/paster_commands/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,13 @@ +# -*- 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 . diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/cache_keys.py --- a/rhodecode/lib/paster_commands/cache_keys.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/paster_commands/cache_keys.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.paster_commands.cache_keys - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - cleanup-keys paster command for RhodeCode - - - :created_on: mar 27, 2013 - :author: marcink - :copyright: (C) 2010-2013 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.paster_commands.cache_keys +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +cleanup-keys paster command for RhodeCode + + +:created_on: mar 27, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/cleanup.py --- a/rhodecode/lib/paster_commands/cleanup.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.lib.paster_commands.cleanup - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - cleanup-repos paster command for RhodeCode - - - :created_on: Jul 14, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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 . -from __future__ import with_statement - -import os -import sys -import re -import shutil -import logging -import datetime - -from rhodecode.lib.utils import BasePasterCommand, ask_ok, REMOVED_REPO_PAT -from rhodecode.lib.utils2 import safe_str -from rhodecode.model.db import RhodeCodeUi - -# fix rhodecode import -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) - -log = logging.getLogger(__name__) - - -class Command(BasePasterCommand): - - max_args = 1 - min_args = 1 - - usage = "CONFIG_FILE" - group_name = "RhodeCode" - takes_config_file = -1 - parser = BasePasterCommand.standard_parser(verbose=True) - summary = "Cleanup deleted repos" - - def _parse_older_than(self, val): - regex = re.compile(r'((?P\d+?)d)?((?P\d+?)h)?((?P\d+?)m)?((?P\d+?)s)?') - parts = regex.match(val) - if not parts: - return - parts = parts.groupdict() - time_params = {} - for (name, param) in parts.iteritems(): - if param: - time_params[name] = int(param) - return datetime.timedelta(**time_params) - - def _extract_date(self, name): - """ - Extract the date part from rm__ pattern of removed repos, - and convert it to datetime object - - :param name: - """ - date_part = name[4:19] # 4:19 since we don't parse milisecods - return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S') - - def command(self): - #get SqlAlchemy session - self._init_session() - - repos_location = RhodeCodeUi.get_repos_location() - to_remove = [] - for dn, dirs, f in os.walk(safe_str(repos_location)): - alldirs = list(dirs) - del dirs[:] - if ('.hg' in alldirs or - 'objects' in alldirs and ('refs' in alldirs or 'packed-refs' in f)): - continue - for loc in alldirs: - if REMOVED_REPO_PAT.match(loc): - to_remove.append([os.path.join(dn, loc), - self._extract_date(loc)]) - else: - dirs.append(loc) - - #filter older than (if present)! - now = datetime.datetime.now() - older_than = self.options.older_than - if older_than: - to_remove_filtered = [] - older_than_date = self._parse_older_than(older_than) - for name, date_ in to_remove: - repo_age = now - date_ - if repo_age > older_than_date: - to_remove_filtered.append([name, date_]) - - to_remove = to_remove_filtered - print >> sys.stdout, 'removing %s deleted repos older than %s (%s)' \ - % (len(to_remove), older_than, older_than_date) - else: - print >> sys.stdout, 'removing all [%s] deleted repos' \ - % len(to_remove) - if self.options.dont_ask or not to_remove: - # don't ask just remove ! - remove = True - else: - remove = ask_ok('the following repositories will be deleted completely:\n%s\n' - 'are you sure you want to remove them [y/n]?' - % ', \n'.join(['%s removed on %s' - % (safe_str(x[0]), safe_str(x[1])) for x in to_remove])) - - if remove: - for path, date_ in to_remove: - print >> sys.stdout, 'removing repository %s' % path - shutil.rmtree(path) - else: - print 'nothing done exiting...' - sys.exit(0) - - def update_parser(self): - self.parser.add_option( - '--older-than', - action='store', - dest='older_than', - help=("only remove repos that have been removed " - "at least given time ago. " - "The default is to remove all removed repositories. " - "Possible suffixes: " - "d (days), h (hours), m (minutes), s (seconds). " - "For example --older-than=30d deletes repositories " - "removed more than 30 days ago.") - ) - - self.parser.add_option( - '--dont-ask', - action="store_true", - dest="dont_ask", - help="remove repositories without asking for confirmation." - ) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/ishell.py --- a/rhodecode/lib/paster_commands/ishell.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/paster_commands/ishell.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.paster_commands.ishell - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - interactive shell paster command for RhodeCode - - - :created_on: Apr 4, 2013 - :author: marcink - :copyright: (C) 2010-2013 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.paster_commands.ishell +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +interactive shell paster command for RhodeCode + +:created_on: Apr 4, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/make_index.py --- a/rhodecode/lib/paster_commands/make_index.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/paster_commands/make_index.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.paster_commands.make_index - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - make-index paster command for RhodeCode - - :created_on: Aug 17, 2010 - :author: marcink - :copyright: (C) 2010-2013 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.paster_commands.make_index +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +make-index paster command for RhodeCode + +:created_on: Aug 17, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + from __future__ import with_statement import os diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/make_rcextensions.py --- a/rhodecode/lib/paster_commands/make_rcextensions.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/paster_commands/make_rcextensions.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.paster_commands.make_rcextensions - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - make-rcext paster command for RhodeCode - - :created_on: Mar 6, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.paster_commands.make_rcextensions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +make-rcext paster command for RhodeCode + +:created_on: Mar 6, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + from __future__ import with_statement import os diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/repo_scan.py --- a/rhodecode/lib/paster_commands/repo_scan.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/paster_commands/repo_scan.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.paster_commands.make_rcextensions - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - repo-scan paster command for RhodeCode - - - :created_on: Feb 9, 2013 - :author: marcink - :copyright: (C) 2010-2013 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.paster_commands.make_rcextensions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +repo-scan paster command for RhodeCode + +:created_on: Feb 9, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/paster_commands/update_repoinfo.py --- a/rhodecode/lib/paster_commands/update_repoinfo.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/paster_commands/update_repoinfo.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.paster_commands.make_rcextensions - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - uodate-repoinfo paster command for RhodeCode - - :created_on: Jul 14, 2012 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.paster_commands.make_rcextensions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +uodate-repoinfo paster command for RhodeCode + +:created_on: Jul 14, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/pidlock.py --- a/rhodecode/lib/pidlock.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/pidlock.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,9 +1,21 @@ +# -*- 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 . + +from __future__ import with_statement import os -import sys -import time import errno -from warnings import warn from multiprocessing.util import Finalize from rhodecode.lib.compat import kill @@ -27,9 +39,8 @@ def __init__(self, file_=None, callbackfn=None, desc='daemon lock', debug=False): - self.pidfile = file_ if file_ else os.path.join( - os.path.dirname(__file__), - 'running.lock') + lock_name = os.path.join(os.path.dirname(__file__), 'running.lock') + self.pidfile = file_ if file_ else lock_name self.callbackfn = callbackfn self.desc = desc self.debug = debug @@ -37,7 +48,7 @@ #run the lock automatically ! self.lock() self._finalize = Finalize(self, DaemonLock._on_finalize, - args=(self, debug), exitpriority=10) + args=(self, debug), exitpriority=10) @staticmethod def _on_finalize(lock, debug): @@ -63,15 +74,15 @@ if self.debug: print 'checking for already running process' try: - pidfile = open(self.pidfile, "r") - pidfile.seek(0) - running_pid = int(pidfile.readline()) - - pidfile.close() + with open(self.pidfile, 'r') as f: + try: + running_pid = int(f.readline()) + except ValueError: + running_pid = -1 if self.debug: print ('lock file present running_pid: %s, ' - 'checking for execution') % running_pid + 'checking for execution' % (running_pid,)) # Now we check the PID from lock file matches to the current # process PID if running_pid: @@ -128,7 +139,6 @@ dir_, file_ = os.path.split(pidfile) if not os.path.isdir(dir_): os.makedirs(dir_) - pidfile = open(self.pidfile, "wb") - pidfile.write(lockname) - pidfile.close + with open(self.pidfile, 'wb') as f: + f.write(lockname) self.held = True diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/rcmail/smtp_mailer.py --- a/rhodecode/lib/rcmail/smtp_mailer.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/rcmail/smtp_mailer.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,14 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.rcmail.smtp_mailer - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Simple smtp mailer used in RhodeCode - - :created_on: Sep 13, 2010 - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -21,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.rcmail.smtp_mailer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Simple smtp mailer used in RhodeCode + +:created_on: Sep 13, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import time import logging import smtplib diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/recaptcha.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/recaptcha.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +import urllib +import urllib2 + +API_SSL_SERVER = "https://www.google.com/recaptcha/api" +API_SERVER = "http://www.google.com/recaptcha/api" +VERIFY_SERVER = "www.google.com" + + +class RecaptchaResponse(object): + def __init__(self, is_valid, error_code=None): + self.is_valid = is_valid + self.error_code = error_code + + def __repr__(self): + return '' % (self.is_valid) + + +def displayhtml(public_key, use_ssl=False, error=None): + """Gets the HTML to display for reCAPTCHA + + public_key -- The public api key + use_ssl -- Should the request be sent over ssl? + error -- An error message to display (from RecaptchaResponse.error_code)""" + + error_param = '' + if error: + error_param = '&error=%s' % error + + if use_ssl: + server = API_SSL_SERVER + else: + server = API_SERVER + + return """ + + +""" % { + 'ApiServer': server, + 'PublicKey': public_key, + 'ErrorParam': error_param, + } + + +def submit(recaptcha_challenge_field, recaptcha_response_field, private_key, + remoteip): + """ + Submits a reCAPTCHA request for verification. Returns RecaptchaResponse + for the request + + recaptcha_challenge_field -- The value of recaptcha_challenge_field from the form + recaptcha_response_field -- The value of recaptcha_response_field from the form + private_key -- your reCAPTCHA private key + remoteip -- the user's ip address + """ + + if not (recaptcha_response_field and recaptcha_challenge_field and + len(recaptcha_response_field) and len( + recaptcha_challenge_field)): + return RecaptchaResponse(is_valid=False, + error_code='incorrect-captcha-sol') + + def encode_if_necessary(s): + if isinstance(s, unicode): + return s.encode('utf-8') + return s + + params = urllib.urlencode({ + 'privatekey': encode_if_necessary(private_key), + 'remoteip': encode_if_necessary(remoteip), + 'challenge': encode_if_necessary(recaptcha_challenge_field), + 'response': encode_if_necessary(recaptcha_response_field), + }) + + request = urllib2.Request( + url="http://%s/recaptcha/api/verify" % VERIFY_SERVER, + data=params, + headers={ + "Content-type": "application/x-www-form-urlencoded", + "User-agent": "reCAPTCHA Python" + } + ) + + httpresp = urllib2.urlopen(request) + + return_values = httpresp.read().splitlines() + httpresp.close() + + return_code = return_values[0] + + if return_code == "true": + return RecaptchaResponse(is_valid=True) + else: + return RecaptchaResponse(is_valid=False, error_code=return_values[1]) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/timerproxy.py --- a/rhodecode/lib/timerproxy.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/timerproxy.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,6 +1,21 @@ -from sqlalchemy.interfaces import ConnectionProxy +# -*- 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 . + import time import logging +from sqlalchemy.interfaces import ConnectionProxy + log = logging.getLogger('timerproxy') BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/utils.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.utils - ~~~~~~~~~~~~~~~~~~~ - - Utilities library for RhodeCode - - :created_on: Apr 18, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.utils +~~~~~~~~~~~~~~~~~~~ + +Utilities library for RhodeCode + +:created_on: Apr 18, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import re @@ -40,6 +40,7 @@ from paste.script.command import Command, BadCommand from webhelpers.text import collapse, remove_formatting, strip_tags +from beaker.cache import _cache_decorate from rhodecode.lib.vcs import get_backend from rhodecode.lib.vcs.backends.base import BaseChangeset @@ -54,10 +55,10 @@ from rhodecode.model.db import Repository, User, RhodeCodeUi, \ UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation, UserGroup from rhodecode.model.meta import Session -from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.model.repo_group import RepoGroupModel from rhodecode.lib.utils2 import safe_str, safe_unicode, get_current_rhodecode_user from rhodecode.lib.vcs.utils.fakemod import create_module -from rhodecode.model.users_group import UserGroupModel +from rhodecode.model.user_group import UserGroupModel log = logging.getLogger(__name__) @@ -110,7 +111,7 @@ return _repo -def get_repos_group_slug(request): +def get_repo_group_slug(request): _group = request.environ['pylons.routes_dict'].get('group_name') if _group: _group = _group.rstrip('/') @@ -131,6 +132,32 @@ return _group +def _extract_id_from_repo_name(repo_name): + if repo_name.startswith('/'): + repo_name = repo_name.lstrip('/') + by_id_match = re.match(r'^_(\d{1,})', repo_name) + if by_id_match: + return by_id_match.groups()[0] + + +def get_repo_by_id(repo_name): + """ + Extracts repo_name by id from special urls. Example url is _11/repo_name + + :param repo_name: + :return: repo_name if matched else None + """ + try: + _repo_id = _extract_id_from_repo_name(repo_name) + if _repo_id: + from rhodecode.model.db import Repository + return Repository.get(_repo_id).repo_name + except Exception: + log.debug('Failed to extract repo_name from URL %s' % ( + traceback.format_exc())) + return + + def action_logger(user, action, repo, ipaddr='', sa=None, commit=False): """ Action logger for various actions made by users @@ -154,14 +181,14 @@ ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '') try: - if hasattr(user, 'user_id'): + if getattr(user, 'user_id', None): user_obj = User.get(user.user_id) elif isinstance(user, basestring): user_obj = User.get_by_username(user) else: raise Exception('You have to provide a user object or a username') - if hasattr(repo, 'repo_id'): + if getattr(repo, 'repo_id', None): repo_obj = Repository.get(repo.repo_id) repo_name = repo_obj.repo_name elif isinstance(repo, basestring): @@ -261,17 +288,17 @@ return False -def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False): +def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False): """ Returns True if given path is a repository group False otherwise :param repo_name: :param base_path: """ - full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name)) + full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name)) # check if it's not a repo - if is_valid_repo(repos_group_name, base_path): + if is_valid_repo(repo_group_name, base_path): return False try: @@ -414,7 +441,7 @@ # last element is repo in nested groups structure groups = groups[:-1] - rgm = ReposGroupModel(sa) + rgm = RepoGroupModel(sa) owner = User.get_first_admin() for lvl, group_name in enumerate(groups): group_name = '/'.join(groups[:lvl] + [group_name]) @@ -455,7 +482,7 @@ from rhodecode.model.repo import RepoModel from rhodecode.model.scm import ScmModel sa = meta.Session() - rm = RepoModel() + repo_model = RepoModel() user = User.get_first_admin() added = [] @@ -468,7 +495,8 @@ for name, repo in initial_repo_list.items(): group = map_groups(name) - db_repo = rm.get_by_repo_name(name) + unicode_name = safe_unicode(name) + db_repo = repo_model.get_by_repo_name(unicode_name) # found repo that is on filesystem not in RhodeCode database if not db_repo: log.info('repository %s not found, creating now' % name) @@ -477,28 +505,32 @@ if repo.description != 'unknown' else '%s repository' % name) - new_repo = rm.create_repo( + new_repo = repo_model._create_repo( repo_name=name, repo_type=repo.alias, description=desc, - repos_group=getattr(group, 'group_id', None), + repo_group=getattr(group, 'group_id', None), owner=user, - just_db=True, enable_locking=enable_locking, enable_downloads=enable_downloads, enable_statistics=enable_statistics, - private=private + private=private, + state=Repository.STATE_CREATED ) + sa.commit() # we added that repo just now, and make sure it has githook - # installed + # installed, and updated server info if new_repo.repo_type == 'git': - ScmModel().install_git_hook(new_repo.scm_instance) + git_repo = new_repo.scm_instance + ScmModel().install_git_hook(git_repo) + # update repository server-info + log.debug('Running update server info') + git_repo._update_server_info() new_repo.update_changeset_cache() elif install_git_hook: if db_repo.repo_type == 'git': ScmModel().install_git_hook(db_repo.scm_instance) - sa.commit() removed = [] if remove_obsolete: # remove from database those repositories that are not in the filesystem @@ -570,10 +602,10 @@ # auto check if the module is not missing any data, set to default if is # this will help autoupdate new feature of rcext module - from rhodecode.config import rcextensions - for k in dir(rcextensions): - if not k.startswith('_') and not hasattr(EXT, k): - setattr(EXT, k, getattr(rcextensions, k)) + #from rhodecode.config import rcextensions + #for k in dir(rcextensions): + # if not k.startswith('_') and not hasattr(EXT, k): + # setattr(EXT, k, getattr(rcextensions, k)) def get_custom_lexer(extension): @@ -639,6 +671,7 @@ dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'], tests=True) dbmanage.create_tables(override=True) + # for tests dynamically set new root paths based on generated content dbmanage.create_settings(dbmanage.config_prompt(repos_test_path)) dbmanage.create_default_user() dbmanage.admin_prompt() @@ -819,3 +852,30 @@ log.warning(msg) log.debug("Returning JSON wrapped action output") return json.dumps(data, encoding='utf-8') + + +def conditional_cache(region, prefix, condition, func): + """ + + Conditional caching function use like:: + def _c(arg): + #heavy computation function + return data + + # denpending from condition the compute is wrapped in cache or not + compute = conditional_cache('short_term', 'cache_desc', codnition=True, func=func) + return compute(arg) + + :param region: name of cache region + :param prefix: cache region prefix + :param condition: condition for cache to be triggered, and return data cached + :param func: wrapped heavy function to compute + + """ + wrapped = func + if condition: + log.debug('conditional_cache: True, wrapping call of ' + 'func: %s into %s region cache' % (region, func)) + wrapped = _cache_decorate((prefix,), None, None, region)(func) + + return wrapped diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/utils2.py --- a/rhodecode/lib/utils2.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/utils2.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.lib.utils - ~~~~~~~~~~~~~~~~~~~ - - Some simple helper functions - - :created_on: Jan 5, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.lib.utils +~~~~~~~~~~~~~~~~~~~ + +Some simple helper functions + +:created_on: Jan 5, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os import re @@ -31,6 +32,8 @@ import datetime import traceback import webob +import urllib +import urlobject from pylons.i18n.translation import _, ungettext from rhodecode.lib.vcs.utils.lazy import LazyProperty @@ -488,6 +491,30 @@ return ''.join(uri) +def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override): + parsed_url = urlobject.URLObject(qualifed_home_url) + decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/'))) + args = { + 'scheme': parsed_url.scheme, + 'user': '', + 'netloc': parsed_url.netloc+decoded_path, # path if we use proxy-prefix + 'prefix': decoded_path, + 'repo': repo_name, + 'repoid': str(repo_id) + } + args.update(override) + args['user'] = urllib.quote(safe_str(args['user'])) + + for k, v in args.items(): + uri_tmpl = uri_tmpl.replace('{%s}' % k, v) + + # remove leading @ sign if it's present. Case of empty user + url_obj = urlobject.URLObject(uri_tmpl) + url = url_obj.with_netloc(url_obj.netloc.lstrip('@')) + + return safe_unicode(url) + + def get_changeset_safe(repo, rev): """ Safe version of get_changeset if this changeset doesn't exists for a @@ -505,7 +532,7 @@ try: cs = repo.get_changeset(rev) - except RepositoryError: + except (RepositoryError, LookupError): cs = EmptyChangeset(requested_revision=rev) return cs @@ -643,6 +670,7 @@ unique_id = int(unique_id / alphabet_length) return "".join(output)[:truncate_to] + def get_current_rhodecode_user(): """ Gets rhodecode user from threadlocal tmpl_context variable if it's @@ -653,3 +681,71 @@ return tmpl_context.rhodecode_user return None + + +class OptionalAttr(object): + """ + Special Optional Option that defines other attribute. Example:: + + def test(apiuser, userid=Optional(OAttr('apiuser')): + user = Optional.extract(userid) + # calls + + """ + + def __init__(self, attr_name): + self.attr_name = attr_name + + def __repr__(self): + return '' % self.attr_name + + def __call__(self): + return self + +#alias +OAttr = OptionalAttr + + +class Optional(object): + """ + Defines an optional parameter:: + + param = param.getval() if isinstance(param, Optional) else param + param = param() if isinstance(param, Optional) else param + + is equivalent of:: + + param = Optional.extract(param) + + """ + + def __init__(self, type_): + self.type_ = type_ + + def __repr__(self): + return '' % self.type_.__repr__() + + def __call__(self): + return self.getval() + + def getval(self): + """ + returns value from this Optional instance + """ + if isinstance(self.type_, OAttr): + # use params name + return self.type_.attr_name + return self.type_ + + @classmethod + def extract(cls, val): + """ + Extracts value from Optional() instance + + :param val: + :return: original value if it's not Optional instance else + value of instance + """ + if isinstance(val, cls): + return val.getval() + return val diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/backends/base.py --- a/rhodecode/lib/vcs/backends/base.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/vcs/backends/base.py Wed Jul 02 19:03:13 2014 -0400 @@ -12,7 +12,7 @@ import datetime import itertools -from rhodecode.lib.vcs.utils import author_name, author_email +from rhodecode.lib.vcs.utils import author_name, author_email, safe_unicode from rhodecode.lib.vcs.utils.lazy import LazyProperty from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs from rhodecode.lib.vcs.conf import settings @@ -99,6 +99,10 @@ def name(self): raise NotImplementedError + @property + def name_unicode(self): + return safe_unicode(self.name) + @LazyProperty def owner(self): raise NotImplementedError @@ -133,6 +137,9 @@ """ raise NotImplementedError + def is_empty(self): + return self._empty + def get_last_change(self): self.get_changesets() @@ -905,6 +912,7 @@ try: old = p.get_node(node.path) missing.remove(node) + # if content actually changed, remove node from unchanged if old.content != node.content: not_changed.remove(node) except NodeDoesNotExistError: @@ -1027,4 +1035,4 @@ return CollectionGenerator(self.repo, sliced_revs) def __repr__(self): - return 'CollectionGenerator<%s>' % (len(self)) + return '' % (len(self)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/backends/git/changeset.py --- a/rhodecode/lib/vcs/backends/git/changeset.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/vcs/backends/git/changeset.py Wed Jul 02 19:03:13 2014 -0400 @@ -189,13 +189,15 @@ """ rev_filter = _git_path = settings.GIT_REV_FILTER so, se = self.repository.run_git_command( - "rev-list %s --children | grep '^%s'" % (rev_filter, self.raw_id) + "rev-list %s --children" % (rev_filter) ) children = [] + pat = re.compile(r'^%s' % self.raw_id) for l in so.splitlines(): - childs = l.split(' ')[1:] - children.extend(childs) + if pat.match(l): + childs = l.split(' ')[1:] + children.extend(childs) return [self.repository.get_changeset(cs) for cs in children] def next(self, branch=None): @@ -294,17 +296,15 @@ f_path = safe_str(path) if limit: - cmd = 'log -n %s --pretty="format: %%H" -s -p %s -- "%s"' % ( - safe_int(limit, 0), cs_id, f_path - ) + cmd = 'log -n %s --pretty="format: %%H" -s %s -- "%s"' % ( + safe_int(limit, 0), cs_id, f_path) else: - cmd = 'log --pretty="format: %%H" -s -p %s -- "%s"' % ( - cs_id, f_path - ) + cmd = 'log --pretty="format: %%H" -s %s -- "%s"' % ( + cs_id, f_path) so, se = self.repository.run_git_command(cmd) ids = re.findall(r'[0-9a-fA-F]{40}', so) - return [self.repository.get_changeset(id) for id in ids] + return [self.repository.get_changeset(sha) for sha in ids] def get_file_history_2(self, path): """ @@ -385,7 +385,7 @@ raise VCSError('You need to pass in a valid stream for filling' ' with archival data') popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, - cwd=self.repository.path) + cwd=self.repository.path) buffer_size = 1024 * 8 chunk = popen.stdout.read(buffer_size) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/backends/git/config.py --- a/rhodecode/lib/vcs/backends/git/config.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,345 +0,0 @@ -# config.py - Reading and writing Git config files -# Copyright (C) 2011 Jelmer Vernooij -# -# 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; version 2 -# of the License or (at your option) a 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, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. - -"""Reading and writing Git configuration files. - -TODO: - * preserve formatting when updating configuration files - * treat subsection names as case-insensitive for [branch.foo] style - subsections -""" - -# Taken from dulwich not yet released 0.8.3 version (until it is actually -# released) - -import errno -import os -import re - -from dulwich.file import GitFile - - -class Config(object): - """A Git configuration.""" - - def get(self, section, name): - """Retrieve the contents of a configuration setting. - - :param section: Tuple with section name and optional subsection namee - :param subsection: Subsection name - :return: Contents of the setting - :raise KeyError: if the value is not set - """ - raise NotImplementedError(self.get) - - def get_boolean(self, section, name, default=None): - """Retrieve a configuration setting as boolean. - - :param section: Tuple with section name and optional subsection namee - :param name: Name of the setting, including section and possible - subsection. - :return: Contents of the setting - :raise KeyError: if the value is not set - """ - try: - value = self.get(section, name) - except KeyError: - return default - if value.lower() == "true": - return True - elif value.lower() == "false": - return False - raise ValueError("not a valid boolean string: %r" % value) - - def set(self, section, name, value): - """Set a configuration value. - - :param name: Name of the configuration value, including section - and optional subsection - :param: Value of the setting - """ - raise NotImplementedError(self.set) - - -class ConfigDict(Config): - """Git configuration stored in a dictionary.""" - - def __init__(self, values=None): - """Create a new ConfigDict.""" - if values is None: - values = {} - self._values = values - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self._values) - - def __eq__(self, other): - return ( - isinstance(other, self.__class__) and - other._values == self._values) - - @classmethod - def _parse_setting(cls, name): - parts = name.split(".") - if len(parts) == 3: - return (parts[0], parts[1], parts[2]) - else: - return (parts[0], None, parts[1]) - - def get(self, section, name): - if isinstance(section, basestring): - section = (section, ) - if len(section) > 1: - try: - return self._values[section][name] - except KeyError: - pass - return self._values[(section[0],)][name] - - def set(self, section, name, value): - if isinstance(section, basestring): - section = (section, ) - self._values.setdefault(section, {})[name] = value - - -def _format_string(value): - if (value.startswith(" ") or - value.startswith("\t") or - value.endswith(" ") or - value.endswith("\t")): - return '"%s"' % _escape_value(value) - return _escape_value(value) - - -def _parse_string(value): - value = value.strip() - ret = [] - block = [] - in_quotes = False - for c in value: - if c == "\"": - in_quotes = (not in_quotes) - ret.append(_unescape_value("".join(block))) - block = [] - elif c in ("#", ";") and not in_quotes: - # the rest of the line is a comment - break - else: - block.append(c) - - if in_quotes: - raise ValueError("value starts with quote but lacks end quote") - - ret.append(_unescape_value("".join(block)).rstrip()) - - return "".join(ret) - - -def _unescape_value(value): - """Unescape a value.""" - def unescape(c): - return { - "\\\\": "\\", - "\\\"": "\"", - "\\n": "\n", - "\\t": "\t", - "\\b": "\b", - }[c.group(0)] - return re.sub(r"(\\.)", unescape, value) - - -def _escape_value(value): - """Escape a value.""" - return value.replace("\\", "\\\\").replace("\n", "\\n")\ - .replace("\t", "\\t").replace("\"", "\\\"") - - -def _check_variable_name(name): - for c in name: - if not c.isalnum() and c != '-': - return False - return True - - -def _check_section_name(name): - for c in name: - if not c.isalnum() and c not in ('-', '.'): - return False - return True - - -def _strip_comments(line): - line = line.split("#")[0] - line = line.split(";")[0] - return line - - -class ConfigFile(ConfigDict): - """A Git configuration file, like .git/config or ~/.gitconfig. - """ - - @classmethod - def from_file(cls, f): - """Read configuration from a file-like object.""" - ret = cls() - section = None - setting = None - for lineno, line in enumerate(f.readlines()): - line = line.lstrip() - if setting is None: - if _strip_comments(line).strip() == "": - continue - if line[0] == "[": - line = _strip_comments(line).rstrip() - if line[-1] != "]": - raise ValueError("expected trailing ]") - key = line.strip() - pts = key[1:-1].split(" ", 1) - pts[0] = pts[0].lower() - if len(pts) == 2: - if pts[1][0] != "\"" or pts[1][-1] != "\"": - raise ValueError( - "Invalid subsection " + pts[1]) - else: - pts[1] = pts[1][1:-1] - if not _check_section_name(pts[0]): - raise ValueError("invalid section name %s" % - pts[0]) - section = (pts[0], pts[1]) - else: - if not _check_section_name(pts[0]): - raise ValueError("invalid section name %s" % - pts[0]) - pts = pts[0].split(".", 1) - if len(pts) == 2: - section = (pts[0], pts[1]) - else: - section = (pts[0], ) - ret._values[section] = {} - else: - if section is None: - raise ValueError("setting %r without section" % line) - try: - setting, value = line.split("=", 1) - except ValueError: - setting = line - value = "true" - setting = setting.strip().lower() - if not _check_variable_name(setting): - raise ValueError("invalid variable name %s" % setting) - if value.endswith("\\\n"): - value = value[:-2] - continuation = True - else: - continuation = False - value = _parse_string(value) - ret._values[section][setting] = value - if not continuation: - setting = None - else: # continuation line - if line.endswith("\\\n"): - line = line[:-2] - continuation = True - else: - continuation = False - value = _parse_string(line) - ret._values[section][setting] += value - if not continuation: - setting = None - return ret - - @classmethod - def from_path(cls, path): - """Read configuration from a file on disk.""" - f = GitFile(path, 'rb') - try: - ret = cls.from_file(f) - ret.path = path - return ret - finally: - f.close() - - def write_to_path(self, path=None): - """Write configuration to a file on disk.""" - if path is None: - path = self.path - f = GitFile(path, 'wb') - try: - self.write_to_file(f) - finally: - f.close() - - def write_to_file(self, f): - """Write configuration to a file-like object.""" - for section, values in self._values.iteritems(): - try: - section_name, subsection_name = section - except ValueError: - (section_name, ) = section - subsection_name = None - if subsection_name is None: - f.write("[%s]\n" % section_name) - else: - f.write("[%s \"%s\"]\n" % (section_name, subsection_name)) - for key, value in values.iteritems(): - f.write("%s = %s\n" % (key, _escape_value(value))) - - -class StackedConfig(Config): - """Configuration which reads from multiple config files..""" - - def __init__(self, backends, writable=None): - self.backends = backends - self.writable = writable - - def __repr__(self): - return "<%s for %r>" % (self.__class__.__name__, self.backends) - - @classmethod - def default_backends(cls): - """Retrieve the default configuration. - - This will look in the repository configuration (if for_path is - specified), the users' home directory and the system - configuration. - """ - paths = [os.path.expanduser("~/.gitconfig"), "/etc/gitconfig"] - backends = [] - for path in paths: - try: - cf = ConfigFile.from_path(path) - except (IOError, OSError), e: - if e.errno != errno.ENOENT: - raise - else: - continue - backends.append(cf) - return backends - - def get(self, section, name): - for backend in self.backends: - try: - return backend.get(section, name) - except KeyError: - pass - raise KeyError(name) - - def set(self, section, name, value): - if self.writable is None: - raise NotImplementedError(self.set) - return self.writable.set(section, name, value) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/backends/git/inmemory.py --- a/rhodecode/lib/vcs/backends/git/inmemory.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/vcs/backends/git/inmemory.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,6 +1,7 @@ import time import datetime import posixpath +import stat from dulwich import objects from dulwich.repo import Repo from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset @@ -38,7 +39,6 @@ object_store = repo.object_store ENCODING = "UTF-8" - DIRMOD = 040000 # Create tree and populates it with blobs commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\ @@ -83,11 +83,11 @@ new_trees.append(curtree) for dirname in reversed_dirnames[:-1]: newtree = objects.Tree() - #newtree.add(DIRMOD, dirname, curtree.id) - newtree[dirname] = DIRMOD, curtree.id + #newtree.add(stat.S_IFDIR, dirname, curtree.id) + newtree[dirname] = stat.S_IFDIR, curtree.id new_trees.append(newtree) curtree = newtree - parent[reversed_dirnames[-1]] = DIRMOD, curtree.id + parent[reversed_dirnames[-1]] = stat.S_IFDIR, curtree.id else: parent.add(name=node_path, mode=node.mode, hexsha=blob.id) @@ -95,7 +95,7 @@ # Update ancestors for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])]): - parent[path] = DIRMOD, tree.id + parent[path] = stat.S_IFDIR, tree.id object_store.add_object(tree) object_store.add_object(blob) @@ -192,8 +192,7 @@ tree = get_tree_for_dir(parent, dirname) if tree is None: tree = objects.Tree() - dirmode = 040000 - parent.add(dirmode, dirname, tree.id) + parent.add(stat.S_IFDIR, dirname, tree.id) parent = tree # Always append tree trees.append(tree) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/backends/git/repository.py --- a/rhodecode/lib/vcs/backends/git/repository.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/vcs/backends/git/repository.py Wed Jul 02 19:03:13 2014 -0400 @@ -20,6 +20,7 @@ from dulwich.objects import Tag from dulwich.repo import Repo, NotGitRepository +from dulwich.config import ConfigFile from rhodecode.lib.vcs import subprocessio from rhodecode.lib.vcs.backends.base import BaseRepository, CollectionGenerator @@ -39,7 +40,6 @@ ) from .changeset import GitChangeset -from .config import ConfigFile from .inmemory import GitInMemoryChangeset from .workdir import GitWorkdir @@ -165,25 +165,31 @@ @classmethod def _check_url(cls, url): """ - Functon will check given url and try to verify if it's a valid - link. Sometimes it may happened that mercurial will issue basic + Function will check given url and try to verify if it's a valid + link. Sometimes it may happened that git will issue basic auth request that can cause whole API to hang when used from python or other external calls. - On failures it'll raise urllib2.HTTPError + On failures it'll raise urllib2.HTTPError, exception is also thrown + when the return code is non 200 """ # check first if it's not an local url if os.path.isdir(url) or url.startswith('file:'): return True - if('+' in url[:url.find('://')]): + if '+' in url[:url.find('://')]: url = url[url.find('+') + 1:] handlers = [] - test_uri, authinfo = hg_url(url).authinfo() + url_obj = hg_url(url) + test_uri, authinfo = url_obj.authinfo() + url_obj.passwd = '*****' + cleaned_uri = str(url_obj) + if not test_uri.endswith('info/refs'): test_uri = test_uri.rstrip('/') + '/info/refs' + if authinfo: #create a password manager passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm() @@ -202,10 +208,19 @@ try: resp = o.open(req) - return resp.code == 200 + if resp.code != 200: + raise Exception('Return Code is not 200') except Exception, e: # means it cannot be cloned - raise urllib2.URLError("[%s] %s" % (url, e)) + raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e)) + + # now detect if it's proper git repo + gitdata = resp.read() + if not 'service=git-upload-pack' in gitdata: + raise urllib2.URLError( + "url [%s] does not look like an git" % (cleaned_uri)) + + return True def _get_repo(self, create, src_url=None, update_after_clone=False, bare=False): @@ -220,7 +235,7 @@ self.clone(src_url, update_after_clone, bare) return Repo(self.path) elif create: - os.mkdir(self.path) + os.makedirs(self.path) if bare: return Repo.init_bare(self.path) else: @@ -274,8 +289,8 @@ try: revision = self.revisions[int(revision)] except Exception: - raise ChangesetDoesNotExistError("Revision %s does not exist " - "for this repository" % (revision)) + msg = ("Revision %s does not exist for %s" % (revision, self)) + raise ChangesetDoesNotExistError(msg) elif is_bstr: # get by branch/tag name @@ -289,8 +304,8 @@ return _tags_shas[_tags_shas.index(revision)] elif not SHA_PATTERN.match(revision) or revision not in self.revisions: - raise ChangesetDoesNotExistError("Revision %s does not exist " - "for this repository" % (revision)) + msg = ("Revision %s does not exist for %s" % (revision, self)) + raise ChangesetDoesNotExistError(msg) # Ensure we return full id if not SHA_PATTERN.match(str(revision)): @@ -348,13 +363,9 @@ @LazyProperty def description(self): - idx_loc = '' if self.bare else '.git' undefined_description = u'unknown' - description_path = os.path.join(self.path, idx_loc, 'description') - if os.path.isfile(description_path): - return safe_unicode(open(description_path).read()) - else: - return undefined_description + _desc = self._repo.get_description() + return safe_unicode(_desc or undefined_description) @LazyProperty def contact(self): @@ -631,7 +642,7 @@ *bare* git repository (no working directory at all). """ url = self._get_url(url) - cmd = ['clone'] + cmd = ['clone', '-q'] if bare: cmd.append('--bare') elif not update_after_clone: @@ -665,6 +676,13 @@ cmd = '''fetch %s -- %s''' % (url, refs) self.run_git_command(cmd) + def _update_server_info(self): + """ + runs gits update-server-info command in this repo instance + """ + from dulwich.server import update_server_info + update_server_info(self._repo) + @LazyProperty def workdir(self): """ diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/backends/hg/repository.py --- a/rhodecode/lib/vcs/backends/hg/repository.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/vcs/backends/hg/repository.py Wed Jul 02 19:03:13 2014 -0400 @@ -33,7 +33,7 @@ from rhodecode.lib.vcs.utils.hgcompat import ( ui, nullid, match, patch, diffopts, clone, get_contact, pull, localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url, - httpbasicauthhandler, httpdigestauthhandler, peer + httpbasicauthhandler, httpdigestauthhandler, peer, httppeer ) from .changeset import MercurialChangeset @@ -288,26 +288,28 @@ context=context))) @classmethod - def _check_url(cls, url): + def _check_url(cls, url, repoui=None): """ Function will check given url and try to verify if it's a valid link. Sometimes it may happened that mercurial will issue basic auth request that can cause whole API to hang when used from python or other external calls. - On failures it'll raise urllib2.HTTPError, return code 200 if url - is valid or True if it's a local path + On failures it'll raise urllib2.HTTPError, exception is also thrown + when the return code is non 200 """ - # check first if it's not an local url if os.path.isdir(url) or url.startswith('file:'): return True - if('+' in url[:url.find('://')]): + if '+' in url[:url.find('://')]: url = url[url.find('+') + 1:] handlers = [] - test_uri, authinfo = hg_url(url).authinfo() + url_obj = hg_url(url) + test_uri, authinfo = url_obj.authinfo() + url_obj.passwd = '*****' + cleaned_uri = str(url_obj) if authinfo: #create a password manager @@ -329,10 +331,21 @@ try: resp = o.open(req) - return resp.code == 200 + if resp.code != 200: + raise Exception('Return Code is not 200') except Exception, e: # means it cannot be cloned - raise urllib2.URLError("[%s] %s" % (url, e)) + raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e)) + + # now check if it's a proper hg repo + try: + repo_id = httppeer(repoui or ui.ui(), url).lookup('tip') + except Exception, e: + raise urllib2.URLError( + "url [%s] does not look like an hg repo org_exc: %s" + % (cleaned_uri, e)) + + return True def _get_repo(self, create, src_url=None, update_after_clone=False): """ @@ -352,7 +365,7 @@ if not update_after_clone: opts.update({'noupdate': True}) try: - MercurialRepository._check_url(url) + MercurialRepository._check_url(url, self.baseui) clone(self.baseui, url, self.path, **opts) # except urllib2.URLError: # raise Abort("Got HTTP 404 error") @@ -378,8 +391,8 @@ @LazyProperty def description(self): undefined_description = u'unknown' - return safe_unicode(self._repo.ui.config('web', 'description', - undefined_description, untrusted=True)) + _desc = self._repo.ui.config('web', 'description', None, untrusted=True) + return safe_unicode(_desc or undefined_description) @LazyProperty def contact(self): @@ -425,10 +438,13 @@ try: revision = hex(self._repo.lookup(revision)) + except (LookupError, ): + msg = ("Ambiguous identifier `%s` for %s" % (revision, self)) + raise ChangesetDoesNotExistError(msg) except (IndexError, ValueError, RepoLookupError, TypeError): - raise ChangesetDoesNotExistError("Revision %s does not " - "exist for this repository" - % (revision)) + msg = ("Revision %s does not exist for %s" % (revision, self)) + raise ChangesetDoesNotExistError(msg) + return revision def _get_archives(self, archive_name='tip'): @@ -490,8 +506,8 @@ "after end revision '%s'" % (start, end)) if branch_name and branch_name not in self.allbranches.keys(): - raise BranchDoesNotExistError('Branch %s not found in' - ' this repository' % branch_name) + msg = ("Branch %s not found in %s" % (branch_name, self)) + raise BranchDoesNotExistError(msg) if end_pos is not None: end_pos += 1 #filter branches diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/nodes.py --- a/rhodecode/lib/vcs/nodes.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/vcs/nodes.py Wed Jul 02 19:03:13 2014 -0400 @@ -326,7 +326,7 @@ return self._mimetype else: raise NodeError('given _mimetype attribute must be an 2 ' - 'element list or tuple') + 'element list or tuple') mtype, encoding = mimetypes.guess_type(self.name) @@ -337,6 +337,17 @@ else: mtype = 'text/plain' encoding = None + + #try with pygments + try: + from pygments.lexers import get_lexer_for_filename + mt = get_lexer_for_filename(self.name).mimetypes + except Exception: + mt = None + + if mt: + mtype = mt[0] + return mtype, encoding @LazyProperty diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/vcs/subprocessio.py --- a/rhodecode/lib/vcs/subprocessio.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/lib/vcs/subprocessio.py Wed Jul 02 19:03:13 2014 -0400 @@ -4,7 +4,7 @@ stream processor exposing the output data as an iterator fitting to be a return value passed by a WSGI applicaiton to a WSGI server per PEP 3333. -Copyright (c) 2011 Daniel Dotsenko +Copyright (c) 2011 Daniel Dotsenko This file is part of git_http_backend.py Project. @@ -34,6 +34,7 @@ without blocking the main thread. We close inpipe once the end of the source stream is reached. """ + def __init__(self, source): super(StreamFeeder, self).__init__() self.daemon = True @@ -129,12 +130,13 @@ if len(t) > ccm: kr.clear() kr.wait(2) -# # this only works on 2.7.x and up -# if not kr.wait(10): -# raise Exception("Timed out while waiting for input to be read.") + # # this only works on 2.7.x and up + # if not kr.wait(10): + # raise Exception("Timed out while waiting for input to be read.") # instead we'll use this if len(t) > ccm + 3: - raise IOError("Timed out while waiting for input from subprocess.") + raise IOError( + "Timed out while waiting for input from subprocess.") t.append(b) da.set() b = s.read(cs) @@ -142,7 +144,7 @@ da.set() # for cases when done but there was no input. -class BufferedGenerator(): +class BufferedGenerator(object): """ Class behaves as a non-blocking, buffered pipe reader. Reads chunks of data (through a thread) @@ -164,7 +166,6 @@ maxlen = None self.data = deque(starting_values, maxlen) - self.worker = InputStreamChunker(source, self.data, buffer_size, chunk_size) if starting_values: @@ -325,6 +326,7 @@ """ + def __init__(self, cmd, inputstream=None, buffer_size=65536, chunk_size=4096, starting_values=[], **kwargs): """ @@ -347,15 +349,14 @@ cmd = ' '.join(cmd) kwargs['shell'] = _shell - _p = subprocess.Popen(cmd, - bufsize=-1, - stdin=inputstream, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - **kwargs - ) + _p = subprocess.Popen(cmd, bufsize=-1, + stdin=inputstream, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs) - bg_out = BufferedGenerator(_p.stdout, buffer_size, chunk_size, starting_values) + bg_out = BufferedGenerator(_p.stdout, buffer_size, chunk_size, + starting_values) bg_err = BufferedGenerator(_p.stderr, 16000, 1, bottomless=True) while not bg_out.done_reading and not bg_out.reading_paused and not bg_err.length: @@ -371,14 +372,16 @@ if _returncode or (_returncode is None and bg_err.length): try: _p.terminate() - except: + except Exception: pass bg_out.stop() bg_err.stop() err = '%s' % ''.join(bg_err) if err: - raise EnvironmentError("Subprocess exited due to an error:\n" + err) - raise EnvironmentError("Subprocess exited with non 0 ret code:%s" % _returncode) + raise EnvironmentError( + "Subprocess exited due to an error:\n" + err) + raise EnvironmentError( + "Subprocess exited with non 0 ret code:%s" % _returncode) self.process = _p self.output = bg_out diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/lib/verlib.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/verlib.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,326 @@ +""" +"Rational" version definition and parsing for DistutilsVersionFight +discussion at PyCon 2009. +""" + +import sys +import re + +class IrrationalVersionError(Exception): + """This is an irrational version.""" + pass + +class HugeMajorVersionNumError(IrrationalVersionError): + """An irrational version because the major version number is huge + (often because a year or date was used). + + See `error_on_huge_major_num` option in `NormalizedVersion` for details. + This guard can be disabled by setting that option False. + """ + pass + +# A marker used in the second and third parts of the `parts` tuple, for +# versions that don't have those segments, to sort properly. An example +# of versions in sort order ('highest' last): +# 1.0b1 ((1,0), ('b',1), ('f',)) +# 1.0.dev345 ((1,0), ('f',), ('dev', 345)) +# 1.0 ((1,0), ('f',), ('f',)) +# 1.0.post256.dev345 ((1,0), ('f',), ('f', 'post', 256, 'dev', 345)) +# 1.0.post345 ((1,0), ('f',), ('f', 'post', 345, 'f')) +# ^ ^ ^ +# 'b' < 'f' ---------------------/ | | +# | | +# 'dev' < 'f' < 'post' -------------------/ | +# | +# 'dev' < 'f' ----------------------------------------------/ +# Other letters would do, but 'f' for 'final' is kind of nice. +FINAL_MARKER = ('f',) + +VERSION_RE = re.compile(r''' + ^ + (?P\d+\.\d+) # minimum 'N.N' + (?P(?:\.\d+)*) # any number of extra '.N' segments + (?: + (?P[abc]|rc) # 'a'=alpha, 'b'=beta, 'c'=release candidate + # 'rc'= alias for release candidate + (?P\d+(?:\.\d+)*) + )? + (?P(\.post(?P\d+))?(\.dev(?P\d+))?)? + $''', re.VERBOSE) + +class NormalizedVersion(object): + """A rational version. + + Good: + 1.2 # equivalent to "1.2.0" + 1.2.0 + 1.2a1 + 1.2.3a2 + 1.2.3b1 + 1.2.3c1 + 1.2.3.4 + TODO: fill this out + + Bad: + 1 # mininum two numbers + 1.2a # release level must have a release serial + 1.2.3b + """ + def __init__(self, s, error_on_huge_major_num=True): + """Create a NormalizedVersion instance from a version string. + + :param s {str} The version string. + :param error_on_huge_major_num {bool} Whether to consider an + apparent use of a year or full date as the major version number + an error. Default True. One of the observed patterns on PyPI before + the introduction of `NormalizedVersion` was version numbers like this: + 2009.01.03 + 20040603 + 2005.01 + This guard is here to strongly encourage the package author to + use an alternate version, because a release deployed into PyPI + and, e.g. downstream Linux package managers, will forever remove + the possibility of using a version number like "1.0" (i.e. + where the major number is less than that huge major number). + """ + self._parse(s, error_on_huge_major_num) + + @classmethod + def from_parts(cls, version, prerelease=FINAL_MARKER, + devpost=FINAL_MARKER): + return cls(cls.parts_to_str((version, prerelease, devpost))) + + def _parse(self, s, error_on_huge_major_num=True): + """Parses a string version into parts.""" + match = VERSION_RE.search(s) + if not match: + raise IrrationalVersionError(s) + + groups = match.groupdict() + parts = [] + + # main version + block = self._parse_numdots(groups['version'], s, False, 2) + extraversion = groups.get('extraversion') + if extraversion not in ('', None): + block += self._parse_numdots(extraversion[1:], s) + parts.append(tuple(block)) + + # prerelease + prerel = groups.get('prerel') + if prerel is not None: + block = [prerel] + block += self._parse_numdots(groups.get('prerelversion'), s, + pad_zeros_length=1) + parts.append(tuple(block)) + else: + parts.append(FINAL_MARKER) + + # postdev + if groups.get('postdev'): + post = groups.get('post') + dev = groups.get('dev') + postdev = [] + if post is not None: + postdev.extend([FINAL_MARKER[0], 'post', int(post)]) + if dev is None: + postdev.append(FINAL_MARKER[0]) + if dev is not None: + postdev.extend(['dev', int(dev)]) + parts.append(tuple(postdev)) + else: + parts.append(FINAL_MARKER) + self.parts = tuple(parts) + if error_on_huge_major_num and self.parts[0][0] > 1980: + raise HugeMajorVersionNumError("huge major version number, %r, " + "which might cause future problems: %r" % (self.parts[0][0], s)) + + def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True, + pad_zeros_length=0): + """Parse 'N.N.N' sequences, return a list of ints. + + :param s {str} 'N.N.N..." sequence to be parsed + :param full_ver_str {str} The full version string from which this + comes. Used for error strings. + :param drop_trailing_zeros {bool} Whether to drop trailing zeros + from the returned list. Default True. + :param pad_zeros_length {int} The length to which to pad the + returned list with zeros, if necessary. Default 0. + """ + nums = [] + for n in s.split("."): + if len(n) > 1 and n[0] == '0': + raise IrrationalVersionError("cannot have leading zero in " + "version number segment: '%s' in %r" % (n, full_ver_str)) + nums.append(int(n)) + if drop_trailing_zeros: + while nums and nums[-1] == 0: + nums.pop() + while len(nums) < pad_zeros_length: + nums.append(0) + return nums + + def __str__(self): + return self.parts_to_str(self.parts) + + @classmethod + def parts_to_str(cls, parts): + """Transforms a version expressed in tuple into its string + representation.""" + # XXX This doesn't check for invalid tuples + main, prerel, postdev = parts + s = '.'.join(str(v) for v in main) + if prerel is not FINAL_MARKER: + s += prerel[0] + s += '.'.join(str(v) for v in prerel[1:]) + if postdev and postdev is not FINAL_MARKER: + if postdev[0] == 'f': + postdev = postdev[1:] + i = 0 + while i < len(postdev): + if i % 2 == 0: + s += '.' + s += str(postdev[i]) + i += 1 + return s + + def __repr__(self): + return "%s('%s')" % (self.__class__.__name__, self) + + def _cannot_compare(self, other): + raise TypeError("cannot compare %s and %s" + % (type(self).__name__, type(other).__name__)) + + def __eq__(self, other): + if not isinstance(other, NormalizedVersion): + self._cannot_compare(other) + return self.parts == other.parts + + def __lt__(self, other): + if not isinstance(other, NormalizedVersion): + self._cannot_compare(other) + return self.parts < other.parts + + def __ne__(self, other): + return not self.__eq__(other) + + def __gt__(self, other): + return not (self.__lt__(other) or self.__eq__(other)) + + def __le__(self, other): + return self.__eq__(other) or self.__lt__(other) + + def __ge__(self, other): + return self.__eq__(other) or self.__gt__(other) + +def suggest_normalized_version(s): + """Suggest a normalized version close to the given version string. + + If you have a version string that isn't rational (i.e. NormalizedVersion + doesn't like it) then you might be able to get an equivalent (or close) + rational version from this function. + + This does a number of simple normalizations to the given string, based + on observation of versions currently in use on PyPI. Given a dump of + those version during PyCon 2009, 4287 of them: + - 2312 (53.93%) match NormalizedVersion without change + - with the automatic suggestion + - 3474 (81.04%) match when using this suggestion method + + :param s {str} An irrational version string. + :returns: A rational version string, or None, if couldn't determine one. + """ + try: + NormalizedVersion(s) + return s # already rational + except IrrationalVersionError: + pass + + rs = s.lower() + + # part of this could use maketrans + for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), + ('beta', 'b'), ('rc', 'c'), ('-final', ''), + ('-pre', 'c'), + ('-release', ''), ('.release', ''), ('-stable', ''), + ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), + ('final', '')): + rs = rs.replace(orig, repl) + + # if something ends with dev or pre, we add a 0 + rs = re.sub(r"pre$", r"pre0", rs) + rs = re.sub(r"dev$", r"dev0", rs) + + # if we have something like "b-2" or "a.2" at the end of the + # version, that is pobably beta, alpha, etc + # let's remove the dash or dot + rs = re.sub(r"([abc|rc])[\-\.](\d+)$", r"\1\2", rs) + + # 1.0-dev-r371 -> 1.0.dev371 + # 0.1-dev-r79 -> 0.1.dev79 + rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) + + # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 + rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) + + # Clean: v0.3, v1.0 + if rs.startswith('v'): + rs = rs[1:] + + # Clean leading '0's on numbers. + #TODO: unintended side-effect on, e.g., "2003.05.09" + # PyPI stats: 77 (~2%) better + rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) + + # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers + # zero. + # PyPI stats: 245 (7.56%) better + rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) + + # the 'dev-rNNN' tag is a dev tag + rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) + + # clean the - when used as a pre delimiter + rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) + + # a terminal "dev" or "devel" can be changed into ".dev0" + rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) + + # a terminal "dev" can be changed into ".dev0" + rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) + + # a terminal "final" or "stable" can be removed + rs = re.sub(r"(final|stable)$", "", rs) + + # The 'r' and the '-' tags are post release tags + # 0.4a1.r10 -> 0.4a1.post10 + # 0.9.33-17222 -> 0.9.3.post17222 + # 0.9.33-r17222 -> 0.9.3.post17222 + rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) + + # Clean 'r' instead of 'dev' usage: + # 0.9.33+r17222 -> 0.9.3.dev17222 + # 1.0dev123 -> 1.0.dev123 + # 1.0.git123 -> 1.0.dev123 + # 1.0.bzr123 -> 1.0.dev123 + # 0.1a0dev.123 -> 0.1a0.dev123 + # PyPI stats: ~150 (~4%) better + rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) + + # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: + # 0.2.pre1 -> 0.2c1 + # 0.2-c1 -> 0.2c1 + # 1.0preview123 -> 1.0c123 + # PyPI stats: ~21 (0.62%) better + rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) + + + # Tcl/Tk uses "px" for their post release markers + rs = re.sub(r"p(\d+)$", r".post\1", rs) + + try: + NormalizedVersion(rs) + return rs # already rational + except IrrationalVersionError: + pass + return None diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/__init__.py --- a/rhodecode/model/__init__.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/__init__.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,33 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.__init__ - ~~~~~~~~~~~~~~~~~~~~~~~~ - - The application's model objects - - :created_on: Nov 25, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. - - - :example: - - .. code-block:: python - - from paste.deploy import appconfig - from pylons import config - from sqlalchemy import engine_from_config - from rhodecode.config.environment import load_environment - - conf = appconfig('config:development.ini', relative_to = './../../') - load_environment(conf.global_conf, conf.local_conf) - - engine = engine_from_config(config, 'sqlalchemy.') - init_model(engine) - # RUN YOUR CODE HERE - -""" # 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 @@ -40,6 +11,36 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.__init__ +~~~~~~~~~~~~~~~~~~~~~~~~ + +The application's model objects + +:created_on: Nov 25, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + + +:example: + + .. code-block:: python + + from paste.deploy import appconfig + from pylons import config + from sqlalchemy import engine_from_config + from rhodecode.config.environment import load_environment + + conf = appconfig('config:development.ini', relative_to = './../../') + load_environment(conf.global_conf, conf.local_conf) + + engine = engine_from_config(config, 'sqlalchemy.') + init_model(engine) + # RUN YOUR CODE HERE + +""" + import logging from rhodecode.model import meta @@ -130,8 +131,9 @@ return self._get_instance(Permission, permission, callback=Permission.get_by_key) - def get_all(self): + @classmethod + def get_all(cls): """ Returns all instances of what is defined in `cls` class variable """ - return self.cls.getAll() + return cls.cls.getAll() diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/api_key.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/api_key.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,86 @@ +# -*- 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 . +""" +rhodecode.model.api_key +~~~~~~~~~~~~~~~~~~~~~~~ + +api key model for RhodeCode + +:created_on: Sep 8, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +from __future__ import with_statement +import time +import logging +import traceback +from sqlalchemy import or_ + +from rhodecode.lib.utils2 import generate_api_key +from rhodecode.model import BaseModel +from rhodecode.model.db import UserApiKeys +from rhodecode.model.meta import Session + +log = logging.getLogger(__name__) + + +class ApiKeyModel(BaseModel): + cls = UserApiKeys + + def create(self, user, description, lifetime=-1): + """ + :param user: user or user_id + :param description: description of ApiKey + :param lifetime: expiration time in seconds + """ + user = self._get_user(user) + + new_api_key = UserApiKeys() + new_api_key.api_key = generate_api_key(user.username) + new_api_key.user_id = user.user_id + new_api_key.description = description + new_api_key.expires = time.time() + (lifetime * 60) if lifetime != -1 else -1 + Session().add(new_api_key) + + return new_api_key + + def delete(self, api_key, user=None): + """ + Deletes given api_key, if user is set it also filters the object for + deletion by given user. + """ + api_key = UserApiKeys.query().filter(UserApiKeys.api_key == api_key) + + if user: + user = self._get_user(user) + api_key = api_key.filter(UserApiKeys.user_id == user.user_id) + + api_key = api_key.scalar() + try: + Session().delete(api_key) + except Exception: + log.error(traceback.format_exc()) + raise + + def get_api_keys(self, user, show_expired=True): + user = self._get_user(user) + user_api_keys = UserApiKeys.query()\ + .filter(UserApiKeys.user_id == user.user_id) + if not show_expired: + user_api_keys = user_api_keys\ + .filter(or_(UserApiKeys.expires == -1, + UserApiKeys.expires >= time.time())) + return user_api_keys diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/changeset_status.py --- a/rhodecode/model/changeset_status.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/changeset_status.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- """ - rhodecode.model.changeset_status - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +rhodecode.model.changeset_status +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Changeset status conttroller - :created_on: Apr 30, 2012 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. +:created_on: Apr 30, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. """ # 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 diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/comment.py --- a/rhodecode/model/comment.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/comment.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.comment - ~~~~~~~~~~~~~~~~~~~~~~~ - - comments model for RhodeCode - - :created_on: Nov 11, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.comment +~~~~~~~~~~~~~~~~~~~~~~~ + +comments model for RhodeCode + +:created_on: Nov 11, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import traceback diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/db.py --- a/rhodecode/model/db.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/db.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.db - ~~~~~~~~~~~~~~~~~~ - - Database Models for RhodeCode - - :created_on: Apr 08, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.db +~~~~~~~~~~~~~~~~~~ + +Database Models for RhodeCode + +:created_on: Apr 08, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import os import time @@ -30,6 +30,7 @@ import traceback import hashlib import collections +import functools from sqlalchemy import * from sqlalchemy.ext.hybrid import hybrid_property @@ -47,7 +48,8 @@ from rhodecode.lib.vcs.backends.base import EmptyChangeset from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras + safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \ + get_clone_url from rhodecode.lib.compat import json from rhodecode.lib.caching_query import FromCache @@ -146,7 +148,10 @@ def __repr__(self): if hasattr(self, '__unicode__'): # python repr needs to return str - return safe_str(self.__unicode__()) + try: + return safe_str(self.__unicode__()) + except UnicodeDecodeError: + pass return '' % (self.__class__.__name__) @@ -155,15 +160,27 @@ __table_args__ = ( UniqueConstraint('app_settings_name'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) + + SETTINGS_TYPES = { + 'str': safe_str, + 'int': safe_int, + 'unicode': safe_unicode, + 'bool': str2bool, + 'list': functools.partial(aslist, sep=',') + } + DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions' + app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, k='', v=''): - self.app_settings_name = k - self.app_settings_value = v + app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None) + _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + + def __init__(self, key='', val='', type='unicode'): + self.app_settings_name = key + self.app_settings_value = val + self.app_settings_type = type @validates('_app_settings_value') def validate_settings_value(self, key, val): @@ -173,13 +190,9 @@ @hybrid_property def app_settings_value(self): v = self._app_settings_value - if self.app_settings_name in ["ldap_active", - "default_repo_enable_statistics", - "default_repo_enable_locking", - "default_repo_private", - "default_repo_enable_downloads"]: - v = str2bool(v) - return v + _type = self.app_settings_type + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + return converter(v) @app_settings_value.setter def app_settings_value(self, val): @@ -190,10 +203,21 @@ """ self._app_settings_value = safe_unicode(val) + @hybrid_property + def app_settings_type(self): + return self._app_settings_type + + @app_settings_type.setter + def app_settings_type(self, val): + if val not in self.SETTINGS_TYPES: + raise Exception('type must be one of %s got %s' + % (self.SETTINGS_TYPES.keys(), val)) + self._app_settings_type = val + def __unicode__(self): - return u"<%s('%s:%s')>" % ( + return u"<%s('%s:%s[%s]')>" % ( self.__class__.__name__, - self.app_settings_name, self.app_settings_value + self.app_settings_name, self.app_settings_value, self.app_settings_type ) @classmethod @@ -202,10 +226,36 @@ .filter(cls.app_settings_name == key).scalar() @classmethod - def get_by_name_or_create(cls, key): + def get_by_name_or_create(cls, key, val='', type='unicode'): res = cls.get_by_name(key) if not res: - res = cls(key) + res = cls(key, val, type) + return res + + @classmethod + def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): + """ + Creates or updates RhodeCode setting. If updates is triggered it will only + update parameters that are explicityl set Optional instance will be skipped + + :param key: + :param val: + :param type: + :return: + """ + res = cls.get_by_name(key) + if not res: + val = Optional.extract(val) + type = Optional.extract(type) + res = cls(key, val, type) + else: + res.app_settings_name = key + if not isinstance(val, Optional): + # update if set + res.app_settings_value = val + if not isinstance(type, Optional): + # update if set + res.app_settings_type = type return res @classmethod @@ -226,9 +276,14 @@ return settings @classmethod - def get_ldap_settings(cls, cache=False): + def get_auth_plugins(cls, cache=False): + auth_plugins = cls.get_by_name("auth_plugins").app_settings_value + return auth_plugins + + @classmethod + def get_auth_settings(cls, cache=False): ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() + .filter(cls.app_settings_name.startswith('auth_')).all() fd = {} for row in ret: fd.update({row.app_settings_name: row.app_settings_value}) @@ -248,13 +303,30 @@ return fd + @classmethod + def get_server_info(cls): + import pkg_resources + import platform + import rhodecode + from rhodecode.lib.utils import check_git_version + mods = [(p.project_name, p.version) for p in pkg_resources.working_set] + info = { + 'modules': sorted(mods, key=lambda k: k[0].lower()), + 'py_version': platform.python_version(), + 'platform': safe_unicode(platform.platform()), + 'rhodecode_version': rhodecode.__version__, + 'git_version': safe_unicode(check_git_version()), + 'git_path': rhodecode.CONFIG.get('git_path') + } + return info + class RhodeCodeUi(Base, BaseModel): __tablename__ = 'rhodecode_ui' __table_args__ = ( UniqueConstraint('ui_key'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) HOOK_UPDATE = 'changegroup.update' @@ -265,9 +337,9 @@ HOOK_PRE_PULL = 'preoutgoing.pre_pull' ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None) ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) # def __init__(self, section='', key='', value=''): @@ -311,8 +383,8 @@ Session().add(new_ui) def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) + return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section, + self.ui_key, self.ui_value) class User(Base, BaseModel): @@ -322,22 +394,26 @@ Index('u_username_idx', 'username'), Index('u_email_idx', 'email'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) DEFAULT_USER = 'default' + DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}' user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None) active = Column("active", Boolean(), nullable=True, unique=None, default=True) admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None) last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data user_log = relationship('UserLog') user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') @@ -358,6 +434,9 @@ user_comments = relationship('ChangesetComment', cascade='all') #extra emails for this user user_emails = relationship('UserEmailMap', cascade='all') + #extra api keys + user_api_keys = relationship('UserApiKeys', cascade='all') + @hybrid_property def email(self): @@ -378,6 +457,11 @@ return [self.email] + [x.email for x in other] @property + def api_keys(self): + other = UserApiKeys.query().filter(UserApiKeys.user==self).all() + return [self.api_key] + [x.api_key for x in other] + + @property def ip_addresses(self): ret = UserIpMap.query().filter(UserIpMap.user == self).all() return [x.ip_addr for x in ret] @@ -416,9 +500,26 @@ return AuthUser(user_id=self.user_id, api_key=self.api_key, username=self.username) + @hybrid_property + def user_data(self): + if not self._user_data: + return {} + + try: + return json.loads(self._user_data) + except TypeError: + return {} + + @user_data.setter + def user_data(self, val): + try: + self._user_data = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + def __unicode__(self): return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) + self.user_id, self.username) @classmethod def get_by_username(cls, username, case_insensitive=False, cache=False): @@ -436,13 +537,24 @@ return q.scalar() @classmethod - def get_by_api_key(cls, api_key, cache=False): + def get_by_api_key(cls, api_key, cache=False, fallback=True): q = cls.query().filter(cls.api_key == api_key) if cache: q = q.options(FromCache("sql_cache_short", "get_api_key_%s" % api_key)) - return q.scalar() + res = q.scalar() + + if fallback and not res: + #fallback to additional keys + _res = UserApiKeys.query()\ + .filter(UserApiKeys.api_key == api_key)\ + .filter(or_(UserApiKeys.expires == -1, + UserApiKeys.expires >= time.time()))\ + .first() + if _res: + res = _res.user + return res @classmethod def get_by_email(cls, email, case_insensitive=False, cache=False): @@ -524,9 +636,11 @@ email=user.email, emails=user.emails, api_key=user.api_key, + api_keys=user.api_keys, active=user.active, admin=user.admin, - ldap_dn=user.ldap_dn, + extern_type=user.extern_type, + extern_name=user.extern_name, last_login=user.last_login, ip_addresses=user.ip_addresses ) @@ -543,19 +657,46 @@ return data +class UserApiKeys(Base, BaseModel): + __tablename__ = 'user_api_keys' + __table_args__ = ( + Index('uak_api_key_idx', 'api_key'), + Index('uak_api_key_expires_idx', 'api_key', 'expires'), + UniqueConstraint('api_key'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} + ) + __mapper_args__ = {} + + user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) + api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True) + description = Column('description', UnicodeText(1024)) + expires = Column('expires', Float(53), nullable=False) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user = relationship('User', lazy='joined') + + @property + def expired(self): + if self.expires == -1: + return False + return time.time() > self.expires + + class UserEmailMap(Base, BaseModel): __tablename__ = 'user_email_map' __table_args__ = ( Index('uem_email_idx', 'email'), UniqueConstraint('email'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) __mapper_args__ = {} email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None) user = relationship('User', lazy='joined') @validates('_email') @@ -580,13 +721,13 @@ __table_args__ = ( UniqueConstraint('user_id', 'ip_addr'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) __mapper_args__ = {} ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None) active = Column("active", Boolean(), nullable=True, unique=None, default=True) user = relationship('User', lazy='joined') @@ -602,20 +743,23 @@ ip_range=self._get_ip_range(self.ip_addr) ) + def __unicode__(self): + return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__, + self.user_id, self.ip_addr) class UserLog(Base, BaseModel): __tablename__ = 'user_logs' __table_args__ = ( {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None) action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) def __unicode__(self): @@ -635,14 +779,17 @@ __tablename__ = 'users_groups' __table_args__ = ( {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") users_group_to_perm = relationship('UserGroupToPerm', cascade='all') @@ -653,6 +800,23 @@ user = relationship('User') + @hybrid_property + def group_data(self): + if not self._group_data: + return {} + + try: + return json.loads(self._group_data) + except TypeError: + return {} + + @group_data.setter + def group_data(self, val): + try: + self._group_data = json.dumps(val) + except Exception: + log.error(traceback.format_exc()) + def __unicode__(self): return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.users_group_id, @@ -674,21 +838,29 @@ return q.scalar() @classmethod - def get(cls, users_group_id, cache=False): - users_group = cls.query() + def get(cls, user_group_id, cache=False): + user_group = cls.query() if cache: - users_group = users_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % users_group_id)) - return users_group.get(users_group_id) - - def get_api_data(self): - users_group = self + user_group = user_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % user_group_id)) + return user_group.get(user_group_id) + + def get_api_data(self, with_members=True): + user_group = self data = dict( - users_group_id=users_group.users_group_id, - group_name=users_group.users_group_name, - active=users_group.users_group_active, + users_group_id=user_group.users_group_id, + group_name=user_group.users_group_name, + group_description=user_group.user_group_description, + active=user_group.users_group_active, + owner=user_group.user.username, ) + if with_members: + members = [] + for user in user_group.members: + user = user.user + members.append(user.get_api_data()) + data['members'] = members return data @@ -697,7 +869,7 @@ __tablename__ = 'users_groups_members' __table_args__ = ( {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -717,16 +889,16 @@ __table_args__ = ( UniqueConstraint('repository_id', 'field_key'), # no-multi field {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) + field_key = Column("field_key", String(250, convert_unicode=False)) + field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False) + field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False) + field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False) field_type = Column("field_type", String(256), nullable=False, unique=None) created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) @@ -756,23 +928,31 @@ UniqueConstraint('repo_name'), Index('r_repo_name_idx', 'repo_name'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) + DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}' + DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}' + + STATE_CREATED = 'repo_state_created' + STATE_PENDING = 'repo_state_pending' + STATE_ERROR = 'repo_state_error' repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) + repo_state = Column("repo_state", String(255), nullable=True) + + clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None) + repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) private = Column("private", Boolean(), nullable=True, unique=None, default=None) enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) + _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None) enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) + _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None) _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) @@ -804,7 +984,24 @@ def __unicode__(self): return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, - self.repo_name) + safe_unicode(self.repo_name)) + + @hybrid_property + def landing_rev(self): + # always should return [rev_type, rev] + if self._landing_revision: + _rev_info = self._landing_revision.split(':') + if len(_rev_info) < 2: + _rev_info.insert(0, 'rev') + return [_rev_info[0], _rev_info[1]] + return [None, None] + + @landing_rev.setter + def landing_rev(self, val): + if ':' not in val: + raise ValueError('value must be delimited with `:` and consist ' + 'of :, got %s instead' % val) + self._landing_revision = val @hybrid_property def locked(self): @@ -1038,25 +1235,48 @@ def last_db_change(self): return self.updated_on + @property + def clone_uri_hidden(self): + clone_uri = self.clone_uri + if clone_uri: + import urlobject + url_obj = urlobject.URLObject(self.clone_uri) + if url_obj.password: + clone_uri = url_obj.with_password('*****') + return clone_uri + def clone_url(self, **override): from pylons import url - from urlparse import urlparse - import urllib - parsed_url = urlparse(url('home', qualified=True)) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - + qualified_home_url = url('home', qualified=True) + + uri_tmpl = None + if 'with_id' in override: + uri_tmpl = self.DEFAULT_CLONE_URI_ID + del override['with_id'] + + if 'uri_tmpl' in override: + uri_tmpl = override['uri_tmpl'] + del override['uri_tmpl'] + + # we didn't override our tmpl from **overrides + if not uri_tmpl: + uri_tmpl = self.DEFAULT_CLONE_URI + try: + from pylons import tmpl_context as c + uri_tmpl = c.clone_uri_tmpl + except Exception: + # in any case if we call this outside of request context, + # ie, not having tmpl_context set up + pass + + return get_clone_url(uri_tmpl=uri_tmpl, + qualifed_home_url=qualified_home_url, + repo_name=self.repo_name, + repo_id=self.repo_id, **override) + + def set_state(self, state): + self.repo_state = state + Session().add(self) #========================================================================== # SCM PROPERTIES #========================================================================== @@ -1068,7 +1288,10 @@ """ Returns landing changeset, or if that doesn't exist returns the tip """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() + _rev_type, _rev = self.landing_rev + cs = self.get_changeset(_rev) + if isinstance(cs, EmptyChangeset): + return self.get_changeset() return cs def update_changeset_cache(self, cs_cache=None): @@ -1233,6 +1456,8 @@ return repo + def __json__(self): + return dict(landing_rev = self.landing_rev) class RepoGroup(Base, BaseModel): __tablename__ = 'groups' @@ -1240,16 +1465,19 @@ UniqueConstraint('group_name', 'group_parent_id'), CheckConstraint('group_id != group_parent_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) __mapper_args__ = {'order_by': 'group_name'} + SEP = ' » ' + group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') @@ -1265,21 +1493,23 @@ self.group_name) @classmethod + def _generate_choice(cls, repo_group): + from webhelpers.html import literal as _literal + _name = lambda k: _literal(cls.SEP.join(k)) + return repo_group.group_id, _name(repo_group.full_path_splitted) + + @classmethod def groups_choices(cls, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal if not groups: groups = cls.query().all() repo_groups = [] if show_empty_group: repo_groups = [('-1', u'-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) + + repo_groups.extend([cls._generate_choice(x) for x in groups]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0]) return repo_groups @classmethod @@ -1304,7 +1534,7 @@ @property def parents(self): - parents_recursion_limit = 5 + parents_recursion_limit = 10 groups = [] if self.parent_group is None: return groups @@ -1319,8 +1549,8 @@ break if cnt == parents_recursion_limit: # this will prevent accidental infinit loops - log.error('group nested more than %s' % - parents_recursion_limit) + log.error(('more than %s parents found for group %s, stopping ' + 'recursive parent fetching' % (parents_recursion_limit, self))) break groups.insert(0, gr) @@ -1399,13 +1629,29 @@ self.parent_group else []) return RepoGroup.url_sep().join(path_prefix + [group_name]) + def get_api_data(self): + """ + Common function for generating api data + + """ + group = self + data = dict( + group_id=group.group_id, + group_name=group.group_name, + group_description=group.group_description, + parent_group=group.parent_group.group_name if group.parent_group else None, + repositories=[x.repo_name for x in group.repositories], + owner=group.user.username + ) + return data + class Permission(Base, BaseModel): __tablename__ = 'permissions' __table_args__ = ( Index('p_perm_name_idx', 'permission_name'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) PERMS = [ ('hg.admin', _('RhodeCode Administrator')), @@ -1433,6 +1679,8 @@ ('hg.create.none', _('Repository creation disabled')), ('hg.create.repository', _('Repository creation enabled')), + ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')), + ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')), ('hg.fork.none', _('Repository forking disabled')), ('hg.fork.repository', _('Repository forking enabled')), @@ -1452,6 +1700,7 @@ 'group.read', 'usergroup.read', 'hg.create.repository', + 'hg.create.write_on_repogroup.true', 'hg.fork.repository', 'hg.register.manual_activate', 'hg.extern_activate.auto', @@ -1488,8 +1737,8 @@ } permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) + permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) def __unicode__(self): return u"<%s('%s:%s')>" % ( @@ -1533,7 +1782,7 @@ __table_args__ = ( UniqueConstraint('user_id', 'repository_id', 'permission_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) @@ -1562,7 +1811,7 @@ __table_args__ = ( UniqueConstraint('user_id', 'user_group_id', 'permission_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) @@ -1591,7 +1840,7 @@ __table_args__ = ( UniqueConstraint('user_id', 'permission_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) @@ -1609,7 +1858,7 @@ __table_args__ = ( UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) @@ -1639,7 +1888,7 @@ UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), CheckConstraint('target_user_group_id != user_group_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) @@ -1668,7 +1917,7 @@ __table_args__ = ( UniqueConstraint('users_group_id', 'permission_id',), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) @@ -1683,7 +1932,7 @@ __table_args__ = ( UniqueConstraint('user_id', 'group_id', 'permission_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -1695,13 +1944,22 @@ group = relationship('RepoGroup') permission = relationship('Permission') + @classmethod + def create(cls, user, repository_group, permission): + n = cls() + n.user = user + n.group = repository_group + n.permission = permission + Session().add(n) + return n + class UserGroupRepoGroupToPerm(Base, BaseModel): __tablename__ = 'users_group_repo_group_to_perm' __table_args__ = ( UniqueConstraint('users_group_id', 'group_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -1713,13 +1971,22 @@ permission = relationship('Permission') group = relationship('RepoGroup') + @classmethod + def create(cls, user_group, repository_group, permission): + n = cls() + n.users_group = user_group + n.group = repository_group + n.permission = permission + Session().add(n) + return n + class Statistics(Base, BaseModel): __tablename__ = 'statistics' __table_args__ = ( UniqueConstraint('repository_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) @@ -1737,7 +2004,7 @@ UniqueConstraint('user_id', 'follows_repository_id'), UniqueConstraint('user_id', 'follows_user_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -1762,14 +2029,14 @@ UniqueConstraint('cache_key'), Index('key_idx', 'cache_key'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) # cache_id, not used cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None) # instance sets cache_active True when it is caching, # other instances set cache_active to False to indicate that this cache is invalid cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) @@ -1825,18 +2092,22 @@ return "%s%s" % (prefix, key) @classmethod - def set_invalidate(cls, repo_name): + def set_invalidate(cls, repo_name, delete=False): """ Mark all caches of a repo as invalid in the database. """ inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - + log.debug('for repo %s got %s invalidation objects' + % (safe_str(repo_name), inv_objs)) try: for inv_obj in inv_objs: log.debug('marking %s key for invalidation based on repo_name=%s' % (inv_obj, safe_str(repo_name))) - inv_obj.cache_active = False - Session().add(inv_obj) + if delete: + Session().delete(inv_obj) + else: + inv_obj.cache_active = False + Session().add(inv_obj) Session().commit() except Exception: log.error(traceback.format_exc()) @@ -1884,7 +2155,7 @@ __table_args__ = ( Index('cc_revision_idx', 'revision'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) @@ -1928,7 +2199,7 @@ Index('cs_version_idx', 'version'), UniqueConstraint('repo_id', 'revision', 'version'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' STATUS_APPROVED = 'approved' @@ -1976,7 +2247,7 @@ __tablename__ = 'pull_requests' __table_args__ = ( {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) # values for .status @@ -2031,7 +2302,7 @@ def __json__(self): return dict( - revisions=self.revisions + revisions=self.revisions ) @@ -2039,7 +2310,7 @@ __tablename__ = 'pull_request_reviewers' __table_args__ = ( {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) def __init__(self, user=None, pull_request=None): @@ -2059,7 +2330,7 @@ __table_args__ = ( Index('notification_type_idx', 'type'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) TYPE_CHANGESET_COMMENT = u'cs_comment' @@ -2116,7 +2387,7 @@ __table_args__ = ( UniqueConstraint('user_id', 'notification_id'), {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} ) user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) @@ -2142,6 +2413,7 @@ ) GIST_PUBLIC = u'public' GIST_PRIVATE = u'private' + DEFAULT_FILENAME = u'gistfile1.txt' gist_id = Column('gist_id', Integer(), primary_key=True) gist_access_id = Column('gist_access_id', Unicode(250)) @@ -2154,6 +2426,9 @@ owner = relationship('User') + def __repr__(self): + return '' % (self.gist_type, self.gist_access_id) + @classmethod def get_or_404(cls, id_): res = cls.query().filter(cls.gist_access_id == id_).scalar() @@ -2222,7 +2497,7 @@ __tablename__ = 'db_migrate_version' __table_args__ = ( {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, + 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, ) repository_id = Column('repository_id', String(250), primary_key=True) repository_path = Column('repository_path', Text) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/forms.py --- a/rhodecode/model/forms.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/forms.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,4 +1,18 @@ -""" this is forms validation classes +# -*- 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 . +""" +this is forms validation classes http://formencode.org/module-formencode.validators.html for list off all availible validators @@ -26,8 +40,8 @@ from pylons.i18n.translation import _ +from rhodecode import BACKENDS from rhodecode.model import validators as v -from rhodecode import BACKENDS log = logging.getLogger(__name__) @@ -58,6 +72,20 @@ chained_validators = [v.ValidAuth()] +def PasswordChangeForm(username): + class _PasswordChangeForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + + current_password = v.ValidOldPassword(username)(not_empty=True) + new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6)) + new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6)) + + chained_validators = [v.ValidPasswordsMatch('new_password', + 'new_password_confirmation')] + return _PasswordChangeForm + + def UserForm(edit=False, old_data={}): class _UserForm(formencode.Schema): allow_extra_fields = True @@ -88,9 +116,9 @@ firstname = v.UnicodeString(strip=True, min=1, not_empty=False) lastname = v.UnicodeString(strip=True, min=1, not_empty=False) email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data)) - + extern_name = v.UnicodeString(strip=True) + extern_type = v.UnicodeString(strip=True) chained_validators = [v.ValidPasswordsMatch()] - return _UserForm @@ -103,6 +131,8 @@ v.UnicodeString(strip=True, min=1, not_empty=True), v.ValidUserGroup(edit, old_data) ) + user_group_description = v.UnicodeString(strip=True, min=1, + not_empty=False) users_group_active = v.StringBoolean(if_missing=False) @@ -115,16 +145,19 @@ return _UserGroupForm -def ReposGroupForm(edit=False, old_data={}, available_groups=[], +def RepoGroupForm(edit=False, old_data={}, available_groups=[], can_create_in_root=False): - class _ReposGroupForm(formencode.Schema): + class _RepoGroupForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), - v.SlugifyName()) + v.SlugifyName(), + v.ValidRegex(msg=_('Name must not contain only digits'))(r'(?!^\d+$)^.+$')) group_description = v.UnicodeString(strip=True, min=1, - not_empty=False) + not_empty=False) + group_copy_permissions = v.StringBoolean(if_missing=False) + if edit: #FIXME: do a special check that we cannot move a group to one of #it's children @@ -134,9 +167,9 @@ testValueList=True, if_missing=None, not_empty=True)) enable_locking = v.StringBoolean(if_missing=False) - chained_validators = [v.ValidReposGroup(edit, old_data)] + chained_validators = [v.ValidRepoGroup(edit, old_data)] - return _ReposGroupForm + return _RepoGroupForm def RegisterForm(edit=False, old_data={}): @@ -187,6 +220,7 @@ repo_description = v.UnicodeString(strip=True, min=1, not_empty=False) repo_private = v.StringBoolean(if_missing=False) repo_landing_rev = v.OneOf(landing_revs, hideList=True) + repo_copy_permissions = v.StringBoolean(if_missing=False) clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False)) repo_enable_statistics = v.StringBoolean(if_missing=False) @@ -196,6 +230,7 @@ if edit: #this is repo owner user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser()) + clone_uri_change = v.UnicodeString(not_empty=False, if_missing=v.Missing) chained_validators = [v.ValidCloneUri(), v.ValidRepoName(edit, old_data)] @@ -210,11 +245,11 @@ return _RepoPermsForm -def RepoGroupPermsForm(): +def RepoGroupPermsForm(valid_recursive_choices): class _RepoGroupPermsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - recursive = v.StringBoolean(if_missing=False) + recursive = v.OneOf(valid_recursive_choices) chained_validators = [v.ValidPerms(type_='repo_group')] return _RepoGroupPermsForm @@ -268,9 +303,11 @@ class _ApplicationSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True) + rhodecode_title = v.UnicodeString(strip=True, not_empty=False) rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True) rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False) + rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False) + rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False) return _ApplicationSettingsForm @@ -286,7 +323,11 @@ rhodecode_repository_fields = v.StringBoolean(if_missing=False) rhodecode_lightweight_journal = v.StringBoolean(if_missing=False) rhodecode_dashboard_items = v.Int(min=5, not_empty=True) + rhodecode_admin_grid_items = v.Int(min=5, not_empty=True) rhodecode_show_version = v.StringBoolean(if_missing=False) + rhodecode_use_gravatar = v.StringBoolean(if_missing=False) + rhodecode_gravatar_url = v.UnicodeString(min=3) + rhodecode_clone_uri_tmpl = v.UnicodeString(min=3) return _ApplicationVisualisationForm @@ -314,8 +355,9 @@ def DefaultPermissionsForm(repo_perms_choices, group_perms_choices, user_group_perms_choices, create_choices, - repo_group_create_choices, user_group_create_choices, - fork_choices, register_choices, extern_activate_choices): + create_on_write_choices, repo_group_create_choices, + user_group_create_choices, fork_choices, + register_choices, extern_activate_choices): class _DefaultPermissionsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True @@ -328,6 +370,7 @@ default_user_group_perm = v.OneOf(user_group_perms_choices) default_repo_create = v.OneOf(create_choices) + create_on_write = v.OneOf(create_on_write_choices) default_user_group_create = v.OneOf(user_group_create_choices) #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet default_fork = v.OneOf(fork_choices) @@ -365,6 +408,42 @@ return _DefaultsForm +def AuthSettingsForm(current_active_modules): + class _AuthSettingsForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + auth_plugins = All(v.ValidAuthPlugins(), + v.UniqueListFromString()(not_empty=True)) + + def __init__(self, *args, **kwargs): + # The auth plugins tell us what form validators they use + if current_active_modules: + import rhodecode.lib.auth_modules + from rhodecode.lib.auth_modules import LazyFormencode + for module in current_active_modules: + plugin = rhodecode.lib.auth_modules.loadplugin(module) + plugin_name = plugin.name + for sv in plugin.plugin_settings(): + newk = "auth_%s_%s" % (plugin_name, sv["name"]) + # can be a LazyFormencode object from plugin settings + validator = sv["validator"] + if isinstance(validator, LazyFormencode): + validator = validator() + #init all lazy validators from formencode.All + if isinstance(validator, All): + init_validators = [] + for validator in validator.validators: + if isinstance(validator, LazyFormencode): + validator = validator() + init_validators.append(validator) + validator.validators = init_validators + + self.add_field(newk, validator) + formencode.Schema.__init__(self, *args, **kwargs) + + return _AuthSettingsForm + + def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices): class _LdapSettingsForm(formencode.Schema): @@ -412,8 +491,8 @@ other_repo = v.UnicodeString(strip=True, required=True) other_ref = v.UnicodeString(strip=True, required=True) revisions = All(#v.NotReviewedRevisions(repo_id)(), - v.UniqueList(not_empty=True)) - review_members = v.UniqueList(not_empty=True) + v.UniqueList()(not_empty=True)) + review_members = v.UniqueList()(not_empty=True) pullrequest_title = v.UnicodeString(strip=True, required=True) pullrequest_desc = v.UnicodeString(strip=True, required=False) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/gist.py --- a/rhodecode/model/gist.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/gist.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.gist - ~~~~~~~~~~~~~~~~~~~~ - - gist model for RhodeCode - - :created_on: May 9, 2013 - :author: marcink - :copyright: (C) 2011-2013 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.gist +~~~~~~~~~~~~~~~~~~~~ + +gist model for RhodeCode + +:created_on: May 9, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os import time @@ -46,6 +47,7 @@ class GistModel(BaseModel): + cls = Gist def _get_gist(self, gist): """ @@ -53,8 +55,7 @@ :param gist: GistID, gist_access_id, or Gist instance """ - return self._get_instance(Gist, gist, - callback=Gist.get_by_access_id) + return self._get_instance(Gist, gist, callback=Gist.get_by_access_id) def __delete_gist(self, gist): """ @@ -64,23 +65,39 @@ """ root_path = RepoModel().repos_path rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id) - log.info("Removing %s" % (rm_path)) + log.info("Removing %s" % (rm_path,)) shutil.rmtree(rm_path) + def _store_metadata(self, repo, gist_id, gist_access_id, user_id, gist_type, + gist_expires): + """ + store metadata inside the gist, this can be later used for imports + or gist identification + """ + metadata = { + 'metadata_version': '1', + 'gist_db_id': gist_id, + 'gist_access_id': gist_access_id, + 'gist_owner_id': user_id, + 'gist_type': gist_type, + 'gist_expires': gist_expires, + 'gist_updated': time.time(), + } + with open(os.path.join(repo.path, '.hg', GIST_METADATA_FILE), 'wb') as f: + f.write(json.dumps(metadata)) + def get_gist(self, gist): return self._get_gist(gist) - def get_gist_files(self, gist_access_id): + def get_gist_files(self, gist_access_id, revision=None): """ Get files for given gist :param gist_access_id: """ repo = Gist.get_by_access_id(gist_access_id) - cs = repo.scm_instance.get_changeset() - return ( - cs, [n for n in cs.get_node('/')] - ) + cs = repo.scm_instance.get_changeset(revision) + return cs, [n for n in cs.get_node('/')] def create(self, description, owner, gist_mapping, gist_type=Gist.GIST_PUBLIC, lifetime=-1): @@ -92,6 +109,7 @@ :param gist_type: type of gist private/public :param lifetime: in minutes, -1 == forever """ + owner = self._get_user(owner) gist_id = safe_unicode(unique_id(20)) lifetime = safe_int(lifetime, -1) gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1 @@ -115,8 +133,8 @@ gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id) log.debug('Creating new %s GIST repo in %s' % (gist_type, gist_repo_path)) - repo = RepoModel()._create_repo(repo_name=gist_repo_path, alias='hg', - parent=None) + repo = RepoModel()._create_filesystem_repo( + repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC) processed_mapping = {} for filename in gist_mapping: @@ -127,7 +145,8 @@ #TODO: expand support for setting explicit lexers # if lexer is None: # try: -# lexer = pygments.lexers.guess_lexer_for_filename(filename,content) +# guess_lexer = pygments.lexers.guess_lexer_for_filename +# lexer = guess_lexer(filename,content) # except pygments.util.ClassNotFound: # lexer = 'text' processed_mapping[filename] = {'content': content} @@ -148,29 +167,74 @@ nodes=processed_mapping, trigger_push_hook=False ) - # store metadata inside the gist, this can be later used for imports - # or gist identification - metadata = { - 'gist_db_id': gist.gist_id, - 'gist_access_id': gist.gist_access_id, - 'gist_owner_id': owner.user_id, - 'gist_type': gist.gist_type, - 'gist_exipres': gist.gist_expires - } - with open(os.path.join(repo.path, '.hg', GIST_METADATA_FILE), 'wb') as f: - f.write(json.dumps(metadata)) + + self._store_metadata(repo, gist.gist_id, gist.gist_access_id, + owner.user_id, gist.gist_type, gist.gist_expires) return gist def delete(self, gist, fs_remove=True): gist = self._get_gist(gist) - try: self.sa.delete(gist) if fs_remove: self.__delete_gist(gist) else: log.debug('skipping removal from filesystem') - except Exception: log.error(traceback.format_exc()) raise + + def update(self, gist, description, owner, gist_mapping, gist_type, + lifetime): + gist = self._get_gist(gist) + gist_repo = gist.scm_instance + + lifetime = safe_int(lifetime, -1) + if lifetime == 0: # preserve old value + gist_expires = gist.gist_expires + else: + gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1 + + #calculate operation type based on given data + gist_mapping_op = {} + for k, v in gist_mapping.items(): + # add, mod, del + if not v['org_filename'] and v['filename']: + op = 'add' + elif v['org_filename'] and not v['filename']: + op = 'del' + else: + op = 'mod' + + v['op'] = op + gist_mapping_op[k] = v + + gist.gist_description = description + gist.gist_expires = gist_expires + gist.owner = owner + gist.gist_type = gist_type + self.sa.add(gist) + self.sa.flush() + + message = 'updated file' + message += 's: ' if len(gist_mapping) > 1 else ': ' + message += ', '.join([x for x in gist_mapping]) + + #fake RhodeCode Repository object + fake_repo = AttributeDict(dict( + repo_name=gist_repo.path, + scm_instance_no_cache=lambda: gist_repo, + )) + + self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id, + owner.user_id, gist.gist_type, gist.gist_expires) + + ScmModel().update_nodes( + user=owner.user_id, + repo=fake_repo, + message=message, + nodes=gist_mapping_op, + trigger_push_hook=False + ) + + return gist diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/license.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/license.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,163 @@ +# -*- 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 . +""" +rhodecode.model.license +~~~~~~~~~~~~~~~~~~~~~~~ + +Model for licenses + + +:created_on: Aug 1, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import uuid +import base64 +import time +import logging +import traceback + +from Crypto import Random +from Crypto.Cipher import AES + +import rhodecode +from rhodecode.lib.compat import json +from rhodecode.model import BaseModel +from rhodecode.model.db import RhodeCodeSetting + +log = logging.getLogger(__name__) + +BLOCK_SIZE = 32 + + +def pad(s): + return (s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * + chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)) + + +def unpad(s): + return s[0:-ord(s[-1])] + + +class LicenseModel(BaseModel): + + cls = RhodeCodeSetting + + def __init__(self, sa=None, key=None): + super(LicenseModel, self).__init__(sa=sa) + if not key: + key = rhodecode.CONFIG.get('license_token') + + if not key: + raise TypeError('Missing encryption key: %s' % key) + if not isinstance(key, basestring): + raise TypeError('Encryption key is bad type, got %s ' + 'expected basestring' % type(key)) + #strip dashes + self.key = key.replace('-', '') + + @classmethod + def generate_license_token(cls): + tok_len = 4 + hex_token = uuid.uuid1().hex + num_pools = [hex_token[start:start+tok_len] + for start in range(0, len(hex_token), tok_len)] + return u'-'.join(num_pools[:4]) + + @classmethod + def get_default_license(cls): + + key = 'abra-cada-bra1-rce3' + enc_license_key = (''' + 0HBgVSMJgapshyHBZGiSj/pqSLkO8BmkbSnFDw8j+e854FkSJuVyUhIyUSFgs5mJ+P + KGknkw/LOf0pKLRZ7EZiFW+lkrv99UaS395FCp4J+ymdg7YdIdYtc0FZ8TcZjXrV3y + ORR3Klsjf7PUV6XVYSU6CAMx5SyK78n3JwYLclCnog1lD4kwHEFJH+OD5jFG3OT9eN + Kd0c1rVIJbbkPfppVIa4twr52bXWjueOpamdFI7DOJtlse9XyY1axgGNVQDtgRlWi5 + YmtP8OfT+Rq9SaCMQzPq3R42/YWHHrG1fhEMO7DAuruyLASoXROAo+hZmDoWYiqOP2 + vtJk/vc6WTB0Ro5zBWznFvkvTPzpFNf+A4FlQzQrVLjVOdIKncyJLx7QFHYFWT9ewD + Abr3XpaN5brqe97hDslN/8uKZabUydYI4dKDYkuc5+WAOfPGuqM/CwIPjZQ6K8ivJK + yX3CHBThwQuJtQY85GHPte0fT0bHoQyLIBwwYa76/pdm4eTaUrMZbj2HipDGTRO6BU + DaDIdw5YiczQ+Jec5phmqwbJ5Z/uXzdV6dhFLDTaiQ+PSkRLg9F1/cPZZxYOo8Jatn + 6pSQvmzi4ALTsaPIGGyu5aazPRB0Wz7g2tyPfUcAP5rzS0aWIdoszsAXizBiJdKgr4 + X2SlOqJ3MYfen4rvLbIQwV2IiRJdtv1QoFGAyyGfDtGzYruZbpfQcouhwRJbaESTwB + 0WXMa73Q2jw59GiTB5C4U=''') + return json.loads(LicenseModel(key=key).decrypt(enc_license_key)) + + @classmethod + def get_license_info(cls, license_token, enc_license_key, safe=True, + fill_defaults=False): + license_info = {} + if fill_defaults: + license_info = cls.get_default_license() + try: + if license_token and enc_license_key: + license_info = json.loads( + LicenseModel(key=license_token).decrypt(enc_license_key)) + except Exception, e: + log.error(traceback.format_exc()) + if not safe: + raise + return license_info + + @classmethod + def get_license_key(cls): + defaults = RhodeCodeSetting.get_app_settings() + return defaults.get('rhodecode_license_key') + + def encrypt(self, text, key=None): + if not isinstance(text, basestring): + raise TypeError('Encrypt can only work on unicode or ' + 'string, got %s' % type(text)) + if not key: + key = self.key + padded_text = pad(text) + IV = Random.new().read(AES.block_size) + cipher = AES.new(key, AES.MODE_CBC, IV) + return base64.b64encode(IV + cipher.encrypt(padded_text)) + + def decrypt(self, enc_text, key=None): + if not isinstance(enc_text, (unicode, str)): + raise TypeError('Encrypt can only work on unicode or ' + 'string, got %s' % type(enc_text)) + if not key: + key = self.key + enc = base64.b64decode(enc_text) + iv = enc[:16] # iv is stored + cipher = AES.new(key, AES.MODE_CBC, iv) + return unpad(cipher.decrypt(enc[16:])) + + def generate_signature(self, license_key, sig_key): + copy = license_key.copy() + del copy['signature'] + return self.encrypt(json.dumps(copy), key=sig_key) + + def verify(self, enc_text, sig_key): + if not isinstance(enc_text, basestring): + raise TypeError('Encrypt can only work on unicode or ' + 'string, got %s' % type(enc_text)) + + decrypted = json.loads(self.decrypt(enc_text)) + try: + signature = json.loads(self.decrypt(decrypted['signature'], sig_key)) + except Exception: + signature = '-- decryption error --' + + del decrypted['signature'] + #TODO: write better diff display + if decrypted != signature: + raise TypeError('Signature mismatch got %s[%s] vs %s[%s]' + % (decrypted, type(decrypted), signature, type(signature))) + return signature diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/meta.py --- a/rhodecode/model/meta.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/meta.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,4 +1,19 @@ -"""SQLAlchemy Metadata and Session object""" +# -*- 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 . +""" +SQLAlchemy Metadata and Session object +""" from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker from beaker import cache diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/notification.py --- a/rhodecode/model/notification.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/notification.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.notification - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Model for notifications - - - :created_on: Nov 20, 2011 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,19 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.notification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Model for notifications + + +:created_on: Nov 20, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import os import logging @@ -255,17 +256,18 @@ TYPE_DEFAULT = 'default' def __init__(self): + super(EmailNotificationModel, self).__init__() self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0] self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup + self.email_types = { + self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html', + self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html', + self.TYPE_REGISTRATION: 'email_templates/registration.html', + self.TYPE_DEFAULT: 'email_templates/default.html', + self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html', + self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html', + } - self.email_types = { - self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html', - self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html', - self.TYPE_REGISTRATION: 'email_templates/registration.html', - self.TYPE_DEFAULT: 'email_templates/default.html', - self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html', - self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html', - } def get_email_tmpl(self, type_, **kwargs): """ diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/permission.py --- a/rhodecode/model/permission.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/permission.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.permission - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - permissions model for RhodeCode - - :created_on: Aug 20, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.permission +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +permissions model for RhodeCode + +:created_on: Aug 20, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback @@ -54,9 +55,10 @@ new_perm.permission_longname = p[0] #translation err with p[1] self.sa.add(new_perm) - def create_default_permissions(self, user): + def create_default_permissions(self, user, force=False): """ - Creates only missing default permissions for user + Creates only missing default permissions for user, if force is set it + resets the default permissions for that user :param user: """ @@ -77,6 +79,11 @@ log.debug('GOT ALREADY DEFINED:%s' % perms) DEFAULT_PERMS = Permission.DEFAULT_USER_PERMISSIONS + if force: + for perm in perms: + self.sa.delete(perm) + self.sa.commit() + defined_perms_groups = [] # for every default permission that needs to be created, we check if # it's group is already defined, if it's not we create default perm for perm_name in DEFAULT_PERMS: @@ -92,7 +99,7 @@ try: # stage 1 set anonymous access - if perm_user.username == 'default': + if perm_user.username == User.DEFAULT_USER: perm_user.active = str2bool(form_result['anonymous']) self.sa.add(perm_user) @@ -112,12 +119,15 @@ for p in u2p: self.sa.delete(p) #create fresh set of permissions - for def_perm_key in ['default_repo_perm', 'default_group_perm', + for def_perm_key in ['default_repo_perm', + 'default_group_perm', 'default_user_group_perm', 'default_repo_create', + 'create_on_write', # special case for create repos on write access to group #'default_repo_group_create', #not implemented yet 'default_user_group_create', - 'default_fork', 'default_register', + 'default_fork', + 'default_register', 'default_extern_activate']: p = _make_new(perm_user, form_result[def_perm_key]) self.sa.add(p) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/pull_request.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.pull_request - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - pull request model for RhodeCode - - :created_on: Jun 6, 2012 - :author: marcink - :copyright: (C) 2012-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.pull_request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +pull request model for RhodeCode + +:created_on: Jun 6, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" import logging import datetime diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/repo.py --- a/rhodecode/model/repo.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/repo.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.repo - ~~~~~~~~~~~~~~~~~~~~ - - Repository model for rhodecode - - :created_on: Jun 5, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,24 +11,40 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.repo +~~~~~~~~~~~~~~~~~~~~ + +Repository model for rhodecode + +:created_on: Jun 5, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. + +""" + from __future__ import with_statement import os import shutil import logging import traceback from datetime import datetime +from rhodecode.lib.utils import make_ui from rhodecode.lib.vcs.backends import get_backend from rhodecode.lib.compat import json -from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\ +from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode, \ remove_prefix, obfuscate_url_pw, get_current_rhodecode_user from rhodecode.lib.caching_query import FromCache from rhodecode.lib.hooks import log_create_repository, log_delete_repository from rhodecode.model import BaseModel -from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \ - Statistics, UserGroup, UserGroupRepoToPerm, RhodeCodeUi, RepoGroup,\ +from rhodecode.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \ + UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, \ + Statistics, UserGroup, RhodeCodeUi, RepoGroup, \ RhodeCodeSetting, RepositoryField + from rhodecode.lib import helpers as h from rhodecode.lib.auth import HasRepoPermissionAny, HasUserGroupPermissionAny from rhodecode.lib.exceptions import AttachedForksError @@ -57,8 +62,8 @@ return self._get_instance(UserGroup, users_group, callback=UserGroup.get_by_group_name) - def _get_repo_group(self, repos_group): - return self._get_instance(RepoGroup, repos_group, + def _get_repo_group(self, repo_group): + return self._get_instance(RepoGroup, repo_group, callback=RepoGroup.get_by_group_name) def _create_default_perms(self, repository, private): @@ -90,7 +95,7 @@ return q.ui_value def get(self, repo_id, cache=False): - repo = self.sa.query(Repository)\ + repo = self.sa.query(Repository) \ .filter(Repository.repo_id == repo_id) if cache: @@ -102,7 +107,7 @@ return self._get_repo(repository) def get_by_repo_name(self, repo_name, cache=False): - repo = self.sa.query(Repository)\ + repo = self.sa.query(Repository) \ .filter(Repository.repo_name == repo_name) if cache: @@ -129,26 +134,26 @@ users = self.sa.query(User).filter(User.active == True).all() return json.dumps([ { - 'id': u.user_id, - 'fname': u.name, - 'lname': u.lastname, - 'nname': u.username, - 'gravatar_lnk': h.gravatar_url(u.email, 14) + 'id': u.user_id, + 'fname': u.name, + 'lname': u.lastname, + 'nname': u.username, + 'gravatar_lnk': h.gravatar_url(u.email, 14) } for u in users] ) - def get_users_groups_js(self): - users_groups = self.sa.query(UserGroup)\ + def get_user_groups_js(self): + user_groups = self.sa.query(UserGroup) \ .filter(UserGroup.users_group_active == True).all() - users_groups = UserGroupList(users_groups, perm_set=['usergroup.read', - 'usergroup.write', - 'usergroup.admin']) + user_groups = UserGroupList(user_groups, perm_set=['usergroup.read', + 'usergroup.write', + 'usergroup.admin']) return json.dumps([ { - 'id': gr.users_group_id, - 'grname': gr.users_group_name, - 'grmembers': len(gr.members), - } for gr in users_groups] + 'id': gr.users_group_id, + 'grname': gr.users_group_name, + 'grmembers': len(gr.members), + } for gr in user_groups] ) @classmethod @@ -179,8 +184,8 @@ def quick_menu(repo_name): return _render('quick_menu', repo_name) - def repo_lnk(name, rtype, private, fork_of): - return _render('repo_name', name, rtype, private, fork_of, + def repo_lnk(name, rtype, rstate, private, fork_of): + return _render('repo_name', name, rtype, rstate, private, fork_of, short_name=not admin, admin=False) def last_change(last_change): @@ -203,6 +208,9 @@ else: return h.urlify_text(h.truncate(desc, 60)) + def state(repo_state): + return _render("repo_state", repo_state) + def repo_actions(repo_name): return _render('repo_actions', repo_name, super_user_actions) @@ -214,7 +222,8 @@ if perm_check: # check permission at this level if not HasRepoPermissionAny( - 'repository.read', 'repository.write', 'repository.admin' + 'repository.read', 'repository.write', + 'repository.admin' )(repo.repo_name, 'get_repos_as_dict check'): continue cs_cache = repo.changeset_cache @@ -222,12 +231,13 @@ "menu": quick_menu(repo.repo_name), "raw_name": repo.repo_name.lower(), "name": repo_lnk(repo.repo_name, repo.repo_type, - repo.private, repo.fork), + repo.repo_state, repo.private, repo.fork), "last_change": last_change(repo.last_db_change), "last_changeset": last_rev(repo.repo_name, cs_cache), - "raw_tip": cs_cache.get('revision'), + "last_rev_raw": cs_cache.get('revision'), "desc": desc(repo.description), "owner": h.person(repo.user.username), + "state": state(repo.repo_state), "rss": rss_lnk(repo.repo_name), "atom": atom_lnk(repo.repo_name), @@ -268,14 +278,19 @@ 'group_id', None) for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'), - (1, 'repo_description'), (1, 'repo_enable_locking'), - (1, 'repo_landing_rev'), (0, 'clone_uri'), - (1, 'repo_private'), (1, 'repo_enable_statistics')]: + (1, 'repo_description'), (1, 'repo_enable_locking'), + (1, 'repo_landing_rev'), (0, 'clone_uri'), + (1, 'repo_private'), (1, 'repo_enable_statistics')]: attr = k if strip: attr = remove_prefix(k, 'repo_') - defaults[k] = defaults[attr] + val = defaults[attr] + if k == 'repo_landing_rev': + val = ':'.join(defaults[attr]) + defaults[k] = val + if k == 'clone_uri': + defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden # fill owner if repo_info.user: @@ -288,33 +303,43 @@ # fill repository users for p in repo_info.repo_to_perm: defaults.update({'u_perm_%s' % p.user.username: - p.permission.permission_name}) + p.permission.permission_name}) # fill repository groups for p in repo_info.users_group_to_perm: defaults.update({'g_perm_%s' % p.users_group.users_group_name: - p.permission.permission_name}) + p.permission.permission_name}) return defaults - def update(self, org_repo_name, **kwargs): + def update(self, repo, **kwargs): try: - cur_repo = self.get_by_repo_name(org_repo_name, cache=False) - + cur_repo = self._get_repo(repo) + org_repo_name = cur_repo.repo_name if 'user' in kwargs: cur_repo.user = User.get_by_username(kwargs['user']) if 'repo_group' in kwargs: cur_repo.group = RepoGroup.get(kwargs['repo_group']) - + log.debug('Updating repo %s with params:%s' % (cur_repo, kwargs)) for strip, k in [(1, 'repo_enable_downloads'), - (1, 'repo_description'), (1, 'repo_enable_locking'), - (1, 'repo_landing_rev'), (0, 'clone_uri'), - (1, 'repo_private'), (1, 'repo_enable_statistics')]: + (1, 'repo_description'), + (1, 'repo_enable_locking'), + (1, 'repo_landing_rev'), + (1, 'repo_private'), + (1, 'repo_enable_statistics'), + (0, 'clone_uri'),]: if k in kwargs: val = kwargs[k] if strip: k = remove_prefix(k, 'repo_') + if k == 'clone_uri': + from rhodecode.model.validators import Missing + _change = kwargs.get('clone_uri_change') + if _change == Missing: + # we don't change the value, so use original one + val = cur_repo.clone_uri + setattr(cur_repo, k, val) new_name = cur_repo.get_new_name(kwargs['repo_name']) @@ -326,8 +351,9 @@ RepoModel().grant_user_permission( repo=cur_repo, user='default', perm=EMPTY_PERM ) - #handle extra fields - for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs): + #handle extra fields + for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), + kwargs): k = RepositoryField.un_prefix_key(field) ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo) if ex_field: @@ -337,29 +363,32 @@ if org_repo_name != new_name: # rename repository - self.__rename_repo(old=org_repo_name, new=new_name) + self._rename_filesystem_repo(old=org_repo_name, new=new_name) return cur_repo except Exception: log.error(traceback.format_exc()) raise - def create_repo(self, repo_name, repo_type, description, owner, - private=False, clone_uri=None, repos_group=None, - landing_rev='tip', just_db=False, fork_of=None, - copy_fork_permissions=False, enable_statistics=False, - enable_locking=False, enable_downloads=False): + def _create_repo(self, repo_name, repo_type, description, owner, + private=False, clone_uri=None, repo_group=None, + landing_rev='rev:tip', fork_of=None, + copy_fork_permissions=False, enable_statistics=False, + enable_locking=False, enable_downloads=False, + copy_group_permissions=False, state=Repository.STATE_PENDING): """ - Create repository + Create repository inside database with PENDING state, this should be + only executed by create() repo. With exception of importing existing repos """ from rhodecode.model.scm import ScmModel owner = self._get_user(owner) fork_of = self._get_repo(fork_of) - repos_group = self._get_repo_group(repos_group) + repo_group = self._get_repo_group(repo_group) try: - + repo_name = safe_unicode(repo_name) + description = safe_unicode(description) # repo name is just a name of repository # while repo_name_full is a full qualified name that is combined # with name and path of group @@ -367,11 +396,12 @@ repo_name = repo_name.split(self.URL_SEPARATOR)[-1] new_repo = Repository() + new_repo.repo_state = state new_repo.enable_statistics = False new_repo.repo_name = repo_name_full new_repo.repo_type = repo_type new_repo.user = owner - new_repo.group = repos_group + new_repo.group = repo_group new_repo.description = description or repo_name new_repo.private = private new_repo.clone_uri = clone_uri @@ -381,8 +411,8 @@ new_repo.enable_locking = enable_locking new_repo.enable_downloads = enable_downloads - if repos_group: - new_repo.enable_locking = repos_group.enable_locking + if repo_group: + new_repo.enable_locking = repo_group.enable_locking if fork_of: parent_repo = fork_of @@ -390,74 +420,62 @@ self.sa.add(new_repo) - if fork_of: - if copy_fork_permissions: - repo = fork_of - user_perms = UserRepoToPerm.query()\ - .filter(UserRepoToPerm.repository == repo).all() - group_perms = UserGroupRepoToPerm.query()\ - .filter(UserGroupRepoToPerm.repository == repo).all() + if fork_of and copy_fork_permissions: + repo = fork_of + user_perms = UserRepoToPerm.query() \ + .filter(UserRepoToPerm.repository == repo).all() + group_perms = UserGroupRepoToPerm.query() \ + .filter(UserGroupRepoToPerm.repository == repo).all() + + for perm in user_perms: + UserRepoToPerm.create(perm.user, new_repo, perm.permission) + + for perm in group_perms: + UserGroupRepoToPerm.create(perm.users_group, new_repo, + perm.permission) + + elif repo_group and copy_group_permissions: - for perm in user_perms: - UserRepoToPerm.create(perm.user, new_repo, - perm.permission) + user_perms = UserRepoGroupToPerm.query() \ + .filter(UserRepoGroupToPerm.group == repo_group).all() + + group_perms = UserGroupRepoGroupToPerm.query() \ + .filter(UserGroupRepoGroupToPerm.group == repo_group).all() - for perm in group_perms: - UserGroupRepoToPerm.create(perm.users_group, new_repo, - perm.permission) - else: - perm_obj = self._create_default_perms(new_repo, private) - self.sa.add(perm_obj) + for perm in user_perms: + perm_name = perm.permission.permission_name.replace('group.', 'repository.') + perm_obj = Permission.get_by_key(perm_name) + UserRepoToPerm.create(perm.user, new_repo, perm_obj) + + for perm in group_perms: + perm_name = perm.permission.permission_name.replace('group.', 'repository.') + perm_obj = Permission.get_by_key(perm_name) + UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj) + else: perm_obj = self._create_default_perms(new_repo, private) self.sa.add(perm_obj) - if not just_db: - self.__create_repo(repo_name, repo_type, - repos_group, - clone_uri) - log_create_repository(new_repo.get_dict(), - created_by=owner.username) - # now automatically start following this repository as owner ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id) + # we need to flush here, in order to check if database won't + # throw any exceptions, create filesystem dirs at the very end + self.sa.flush() return new_repo except Exception: log.error(traceback.format_exc()) raise - def create(self, form_data, cur_user, just_db=False, fork=None): + def create(self, form_data, cur_user): """ - Backward compatibility function, just a wrapper on top of create_repo + Create repository using celery tasks :param form_data: :param cur_user: - :param just_db: - :param fork: """ - owner = cur_user - repo_name = form_data['repo_name_full'] - repo_type = form_data['repo_type'] - description = form_data['repo_description'] - private = form_data['repo_private'] - clone_uri = form_data.get('clone_uri') - repos_group = form_data['repo_group'] - landing_rev = form_data['repo_landing_rev'] - copy_fork_permissions = form_data.get('copy_permissions') - fork_of = form_data.get('fork_parent_id') - - ## repo creation defaults, private and repo_type are filled in form - defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) - enable_statistics = defs.get('repo_enable_statistics') - enable_locking = defs.get('repo_enable_locking') - enable_downloads = defs.get('repo_enable_downloads') - - return self.create_repo( - repo_name, repo_type, description, owner, private, clone_uri, - repos_group, landing_rev, just_db, fork_of, copy_fork_permissions, - enable_statistics, enable_locking, enable_downloads - ) + from rhodecode.lib.celerylib import tasks, run_task + return run_task(tasks.create_repo, form_data, cur_user) def _update_permissions(self, repo, perms_new=None, perms_updates=None, check_perms=True): @@ -475,12 +493,14 @@ ) else: #check if we have permissions to alter this usergroup - req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') - if not check_perms or HasUserGroupPermissionAny(*req_perms)(member): - self.grant_users_group_permission( + req_perms = ( + 'usergroup.read', 'usergroup.write', 'usergroup.admin') + if not check_perms or HasUserGroupPermissionAny(*req_perms)( + member): + self.grant_user_group_permission( repo=repo, group_name=member, perm=perm ) - # set new permissions + # set new permissions for member, perm, member_type in perms_new: if member_type == 'user': self.grant_user_permission( @@ -488,9 +508,11 @@ ) else: #check if we have permissions to alter this usergroup - req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') - if not check_perms or HasUserGroupPermissionAny(*req_perms)(member): - self.grant_users_group_permission( + req_perms = ( + 'usergroup.read', 'usergroup.write', 'usergroup.admin') + if not check_perms or HasUserGroupPermissionAny(*req_perms)( + member): + self.grant_user_group_permission( repo=repo, group_name=member, perm=perm ) @@ -502,7 +524,7 @@ :param cur_user: """ from rhodecode.lib.celerylib import tasks, run_task - run_task(tasks.create_repo_fork, form_data, cur_user) + return run_task(tasks.create_repo_fork, form_data, cur_user) def delete(self, repo, forks=None, fs_remove=True, cur_user=None): """ @@ -533,7 +555,7 @@ try: self.sa.delete(repo) if fs_remove: - self.__delete_repo(repo) + self._delete_filesystem_repo(repo) else: log.debug('skipping removal from filesystem') log_delete_repository(old_repo_dict, @@ -556,9 +578,9 @@ permission = self._get_perm(perm) # check if we have that permission already - obj = self.sa.query(UserRepoToPerm)\ - .filter(UserRepoToPerm.user == user)\ - .filter(UserRepoToPerm.repository == repo)\ + obj = self.sa.query(UserRepoToPerm) \ + .filter(UserRepoToPerm.user == user) \ + .filter(UserRepoToPerm.repository == repo) \ .scalar() if obj is None: # create new ! @@ -568,6 +590,7 @@ obj.permission = permission self.sa.add(obj) log.debug('Granted perm %s to %s on %s' % (perm, user, repo)) + return obj def revoke_user_permission(self, repo, user): """ @@ -580,15 +603,15 @@ user = self._get_user(user) repo = self._get_repo(repo) - obj = self.sa.query(UserRepoToPerm)\ - .filter(UserRepoToPerm.repository == repo)\ - .filter(UserRepoToPerm.user == user)\ + obj = self.sa.query(UserRepoToPerm) \ + .filter(UserRepoToPerm.repository == repo) \ + .filter(UserRepoToPerm.user == user) \ .scalar() if obj: self.sa.delete(obj) log.debug('Revoked perm on %s on %s' % (repo, user)) - def grant_users_group_permission(self, repo, group_name, perm): + def grant_user_group_permission(self, repo, group_name, perm): """ Grant permission for user group on given repository, or update existing one if found @@ -603,9 +626,9 @@ permission = self._get_perm(perm) # check if we have that permission already - obj = self.sa.query(UserGroupRepoToPerm)\ - .filter(UserGroupRepoToPerm.users_group == group_name)\ - .filter(UserGroupRepoToPerm.repository == repo)\ + obj = self.sa.query(UserGroupRepoToPerm) \ + .filter(UserGroupRepoToPerm.users_group == group_name) \ + .filter(UserGroupRepoToPerm.repository == repo) \ .scalar() if obj is None: @@ -617,8 +640,9 @@ obj.permission = permission self.sa.add(obj) log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo)) + return obj - def revoke_users_group_permission(self, repo, group_name): + def revoke_user_group_permission(self, repo, group_name): """ Revoke permission for user group on given repository @@ -629,9 +653,9 @@ repo = self._get_repo(repo) group_name = self._get_user_group(group_name) - obj = self.sa.query(UserGroupRepoToPerm)\ - .filter(UserGroupRepoToPerm.repository == repo)\ - .filter(UserGroupRepoToPerm.users_group == group_name)\ + obj = self.sa.query(UserGroupRepoToPerm) \ + .filter(UserGroupRepoToPerm.repository == repo) \ + .filter(UserGroupRepoToPerm.users_group == group_name) \ .scalar() if obj: self.sa.delete(obj) @@ -645,21 +669,16 @@ """ repo = self._get_repo(repo_name) try: - obj = self.sa.query(Statistics)\ - .filter(Statistics.repository == repo).scalar() + obj = self.sa.query(Statistics) \ + .filter(Statistics.repository == repo).scalar() if obj: self.sa.delete(obj) except Exception: log.error(traceback.format_exc()) raise - def _create_repo(self, repo_name, alias, parent, clone_uri=False, - repo_store_location=None): - return self.__create_repo(repo_name, alias, parent, clone_uri, - repo_store_location) - - def __create_repo(self, repo_name, alias, parent, clone_uri=False, - repo_store_location=None): + def _create_filesystem_repo(self, repo_name, repo_type, repo_group, + clone_uri=None, repo_store_location=None): """ makes repository on filesystem. It's group aware means it'll create a repository within a group, and alter the paths accordingly of @@ -667,22 +686,26 @@ :param repo_name: :param alias: - :param parent_id: + :param parent: :param clone_uri: - :param repo_path: + :param repo_store_location: """ - from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group + from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group from rhodecode.model.scm import ScmModel - if parent: - new_parent_path = os.sep.join(parent.full_path_splitted) + if '/' in repo_name: + raise ValueError('repo_name must not contain groups got `%s`' % repo_name) + + if isinstance(repo_group, RepoGroup): + new_parent_path = os.sep.join(repo_group.full_path_splitted) else: - new_parent_path = '' + new_parent_path = repo_group or '' + if repo_store_location: _paths = [repo_store_location] else: _paths = [self.repos_path, new_parent_path, repo_name] - # we need to make it str for mercurial + # we need to make it str for mercurial repo_path = os.path.join(*map(lambda x: safe_str(x), _paths)) # check if this path is not a repository @@ -690,26 +713,35 @@ raise Exception('This path %s is a valid repository' % repo_path) # check if this path is a group - if is_valid_repos_group(repo_path, self.repos_path): + if is_valid_repo_group(repo_path, self.repos_path): raise Exception('This path %s is a valid group' % repo_path) - log.info('creating repo %s in %s @ %s' % ( - repo_name, safe_unicode(repo_path), - obfuscate_url_pw(clone_uri) - ) - ) - backend = get_backend(alias) - if alias == 'hg': - repo = backend(repo_path, create=True, src_url=clone_uri) - elif alias == 'git': + log.info('creating repo %s in %s from url: `%s`' % ( + repo_name, safe_unicode(repo_path), + obfuscate_url_pw(clone_uri))) + + backend = get_backend(repo_type) + + if repo_type == 'hg': + baseui = make_ui('db', clear_session=False) + # patch and reset hooks section of UI config to not run any + # hooks on creating remote repo + for k, v in baseui.configitems('hooks'): + baseui.setconfig('hooks', k, None) + + repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui) + elif repo_type == 'git': repo = backend(repo_path, create=True, src_url=clone_uri, bare=True) # add rhodecode hook into this repo ScmModel().install_git_hook(repo=repo) else: - raise Exception('Undefined alias %s' % alias) + raise Exception('Not supported repo_type %s expected hg/git' % repo_type) + + log.debug('Created repo %s with %s backend' + % (safe_unicode(repo_name), safe_unicode(repo_type))) return repo - def __rename_repo(self, old, new): + def _rename_filesystem_repo(self, old, new): """ renames repository on filesystem @@ -726,7 +758,7 @@ ) shutil.move(old_path, new_path) - def __delete_repo(self, repo): + def _delete_filesystem_repo(self, repo): """ removes repo from filesystem, the removal is acctually made by added rm__ prefix into dir, and rename internat .hg/.git dirs so this @@ -736,17 +768,19 @@ :param repo: repo object """ rm_path = os.path.join(self.repos_path, repo.repo_name) - log.info("Removing %s" % (rm_path)) + log.info("Removing repository %s" % (rm_path,)) # disable hg/git internal that it doesn't get detected as repo alias = repo.repo_type bare = getattr(repo.scm_instance, 'bare', False) + # skip this for bare git repos if not bare: - # skip this for bare git repos - shutil.move(os.path.join(rm_path, '.%s' % alias), - os.path.join(rm_path, 'rm__.%s' % alias)) - # disable repo + # disable VCS repo + vcs_path = os.path.join(rm_path, '.%s' % alias) + if os.path.exists(vcs_path): + shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias)) + _now = datetime.now() _ms = str(_now.microsecond).rjust(6, '0') _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms), @@ -754,4 +788,6 @@ if repo.group: args = repo.group.full_path_splitted + [_d] _d = os.path.join(*args) - shutil.move(rm_path, os.path.join(self.repos_path, _d)) + + if os.path.isdir(rm_path): + shutil.move(rm_path, os.path.join(self.repos_path, _d)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/repo_group.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/repo_group.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,540 @@ +# -*- 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 . +""" +rhodecode.model.user_group +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +repo group model for RhodeCode + +:created_on: Jan 25, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + + +import os +import logging +import traceback +import shutil +import datetime + +from rhodecode.lib.utils2 import LazyProperty + +from rhodecode.model import BaseModel +from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \ + User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository + +log = logging.getLogger(__name__) + + +class RepoGroupModel(BaseModel): + + cls = RepoGroup + + def _get_user_group(self, users_group): + return self._get_instance(UserGroup, users_group, + callback=UserGroup.get_by_group_name) + + def _get_repo_group(self, repo_group): + return self._get_instance(RepoGroup, repo_group, + callback=RepoGroup.get_by_group_name) + + @LazyProperty + def repos_path(self): + """ + Gets the repositories root path from database + """ + + q = RhodeCodeUi.get_by_key('/') + return q.ui_value + + def _create_default_perms(self, new_group): + # create default permission + default_perm = 'group.read' + def_user = User.get_default_user() + for p in def_user.user_perms: + if p.permission.permission_name.startswith('group.'): + default_perm = p.permission.permission_name + break + + repo_group_to_perm = UserRepoGroupToPerm() + repo_group_to_perm.permission = Permission.get_by_key(default_perm) + + repo_group_to_perm.group = new_group + repo_group_to_perm.user_id = def_user.user_id + return repo_group_to_perm + + def _create_group(self, group_name): + """ + makes repository group on filesystem + + :param repo_name: + :param parent_id: + """ + + create_path = os.path.join(self.repos_path, group_name) + log.debug('creating new group in %s' % create_path) + + if os.path.isdir(create_path): + raise Exception('That directory already exists !') + + os.makedirs(create_path) + log.debug('Created group in %s' % create_path) + + def _rename_group(self, old, new): + """ + Renames a group on filesystem + + :param group_name: + """ + + if old == new: + log.debug('skipping group rename') + return + + log.debug('renaming repository group from %s to %s' % (old, new)) + + old_path = os.path.join(self.repos_path, old) + new_path = os.path.join(self.repos_path, new) + + log.debug('renaming repos paths from %s to %s' % (old_path, new_path)) + + if os.path.isdir(new_path): + raise Exception('Was trying to rename to already ' + 'existing dir %s' % new_path) + shutil.move(old_path, new_path) + + def _delete_group(self, group, force_delete=False): + """ + Deletes a group from a filesystem + + :param group: instance of group from database + :param force_delete: use shutil rmtree to remove all objects + """ + paths = group.full_path.split(RepoGroup.url_sep()) + paths = os.sep.join(paths) + + rm_path = os.path.join(self.repos_path, paths) + log.info("Removing group %s" % (rm_path)) + # delete only if that path really exists + if os.path.isdir(rm_path): + if force_delete: + shutil.rmtree(rm_path) + else: + #archive that group` + _now = datetime.datetime.now() + _ms = str(_now.microsecond).rjust(6, '0') + _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms), + group.name) + shutil.move(rm_path, os.path.join(self.repos_path, _d)) + + def create(self, group_name, group_description, owner, parent=None, + just_db=False, copy_permissions=False): + try: + user = self._get_user(owner) + parent_group = self._get_repo_group(parent) + new_repo_group = RepoGroup() + new_repo_group.user = user + new_repo_group.group_description = group_description or group_name + new_repo_group.parent_group = parent_group + new_repo_group.group_name = new_repo_group.get_new_name(group_name) + + self.sa.add(new_repo_group) + + # create an ADMIN permission for owner except if we're super admin, + # later owner should go into the owner field of groups + if not user.is_admin: + self.grant_user_permission(repo_group=new_repo_group, + user=owner, perm='group.admin') + + if parent_group and copy_permissions: + # copy permissions from parent + user_perms = UserRepoGroupToPerm.query() \ + .filter(UserRepoGroupToPerm.group == parent_group).all() + + group_perms = UserGroupRepoGroupToPerm.query() \ + .filter(UserGroupRepoGroupToPerm.group == parent_group).all() + + for perm in user_perms: + # don't copy over the permission for user who is creating + # this group, if he is not super admin he get's admin + # permission set above + if perm.user != user or user.is_admin: + UserRepoGroupToPerm.create(perm.user, new_repo_group, perm.permission) + + for perm in group_perms: + UserGroupRepoGroupToPerm.create(perm.users_group, new_repo_group, perm.permission) + else: + perm_obj = self._create_default_perms(new_repo_group) + self.sa.add(perm_obj) + + if not just_db: + # we need to flush here, in order to check if database won't + # throw any exceptions, create filesystem dirs at the very end + self.sa.flush() + self._create_group(new_repo_group.group_name) + + return new_repo_group + except Exception: + log.error(traceback.format_exc()) + raise + + def _update_permissions(self, repo_group, perms_new=None, + perms_updates=None, recursive=None, + check_perms=True): + from rhodecode.model.repo import RepoModel + from rhodecode.lib.auth import HasUserGroupPermissionAny + + if not perms_new: + perms_new = [] + if not perms_updates: + perms_updates = [] + + def _set_perm_user(obj, user, perm): + if isinstance(obj, RepoGroup): + self.grant_user_permission(repo_group=obj, user=user, perm=perm) + elif isinstance(obj, Repository): + # private repos will not allow to change the default permissions + # using recursive mode + if obj.private and user == User.DEFAULT_USER: + return + + # we set group permission but we have to switch to repo + # permission + perm = perm.replace('group.', 'repository.') + RepoModel().grant_user_permission( + repo=obj, user=user, perm=perm + ) + + def _set_perm_group(obj, users_group, perm): + if isinstance(obj, RepoGroup): + self.grant_user_group_permission(repo_group=obj, + group_name=users_group, + perm=perm) + elif isinstance(obj, Repository): + # we set group permission but we have to switch to repo + # permission + perm = perm.replace('group.', 'repository.') + RepoModel().grant_user_group_permission( + repo=obj, group_name=users_group, perm=perm + ) + + # start updates + updates = [] + log.debug('Now updating permissions for %s in recursive mode:%s' + % (repo_group, recursive)) + + for obj in repo_group.recursive_groups_and_repos(): + # iterated obj is an instance of a repos group or repository in + # that group, recursive option can be: none, repos, groups, all + if recursive == 'all': + obj = obj + elif recursive == 'repos': + # skip groups, other than this one + if isinstance(obj, RepoGroup) and not obj == repo_group: + continue + elif recursive == 'groups': + # skip repos + if isinstance(obj, Repository): + continue + else: # recursive == 'none': # DEFAULT don't apply to iterated objects + obj = repo_group + # also we do a break at the end of this loop. + + # update permissions + for member, perm, member_type in perms_updates: + ## set for user + if member_type == 'user': + # this updates also current one if found + _set_perm_user(obj, user=member, perm=perm) + ## set for user group + else: + #check if we have permissions to alter this usergroup + req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') + if not check_perms or HasUserGroupPermissionAny(*req_perms)(member): + _set_perm_group(obj, users_group=member, perm=perm) + # set new permissions + for member, perm, member_type in perms_new: + if member_type == 'user': + _set_perm_user(obj, user=member, perm=perm) + else: + #check if we have permissions to alter this usergroup + req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') + if not check_perms or HasUserGroupPermissionAny(*req_perms)(member): + _set_perm_group(obj, users_group=member, perm=perm) + updates.append(obj) + # if it's not recursive call for all,repos,groups + # break the loop and don't proceed with other changes + if recursive not in ['all', 'repos', 'groups']: + break + + return updates + + def update(self, repo_group, form_data): + + try: + repo_group = self._get_repo_group(repo_group) + old_path = repo_group.full_path + + # change properties + repo_group.group_description = form_data['group_description'] + repo_group.group_parent_id = form_data['group_parent_id'] + repo_group.enable_locking = form_data['enable_locking'] + + repo_group.parent_group = RepoGroup.get(form_data['group_parent_id']) + repo_group.group_name = repo_group.get_new_name(form_data['group_name']) + new_path = repo_group.full_path + self.sa.add(repo_group) + + # iterate over all members of this groups and do fixes + # set locking if given + # if obj is a repoGroup also fix the name of the group according + # to the parent + # if obj is a Repo fix it's name + # this can be potentially heavy operation + for obj in repo_group.recursive_groups_and_repos(): + #set the value from it's parent + obj.enable_locking = repo_group.enable_locking + if isinstance(obj, RepoGroup): + new_name = obj.get_new_name(obj.name) + log.debug('Fixing group %s to new name %s' \ + % (obj.group_name, new_name)) + obj.group_name = new_name + elif isinstance(obj, Repository): + # we need to get all repositories from this new group and + # rename them accordingly to new group path + new_name = obj.get_new_name(obj.just_name) + log.debug('Fixing repo %s to new name %s' \ + % (obj.repo_name, new_name)) + obj.repo_name = new_name + self.sa.add(obj) + + self._rename_group(old_path, new_path) + + return repo_group + except Exception: + log.error(traceback.format_exc()) + raise + + def delete(self, repo_group, force_delete=False): + repo_group = self._get_repo_group(repo_group) + try: + self.sa.delete(repo_group) + self._delete_group(repo_group, force_delete) + except Exception: + log.error('Error removing repo_group %s' % repo_group) + raise + + def add_permission(self, repo_group, obj, obj_type, perm, recursive): + from rhodecode.model.repo import RepoModel + repo_group = self._get_repo_group(repo_group) + perm = self._get_perm(perm) + + for el in repo_group.recursive_groups_and_repos(): + # iterated obj is an instance of a repos group or repository in + # that group, recursive option can be: none, repos, groups, all + if recursive == 'all': + el = el + elif recursive == 'repos': + # skip groups, other than this one + if isinstance(el, RepoGroup) and not el == repo_group: + continue + elif recursive == 'groups': + # skip repos + if isinstance(el, Repository): + continue + else: # recursive == 'none': # DEFAULT don't apply to iterated objects + el = repo_group + # also we do a break at the end of this loop. + + if isinstance(el, RepoGroup): + if obj_type == 'user': + RepoGroupModel().grant_user_permission(el, user=obj, perm=perm) + elif obj_type == 'user_group': + RepoGroupModel().grant_user_group_permission(el, group_name=obj, perm=perm) + else: + raise Exception('undefined object type %s' % obj_type) + elif isinstance(el, Repository): + # for repos we need to hotfix the name of permission + _perm = perm.permission_name.replace('group.', 'repository.') + if obj_type == 'user': + RepoModel().grant_user_permission(el, user=obj, perm=_perm) + elif obj_type == 'user_group': + RepoModel().grant_user_group_permission(el, group_name=obj, perm=_perm) + else: + raise Exception('undefined object type %s' % obj_type) + else: + raise Exception('el should be instance of Repository or ' + 'RepositoryGroup got %s instead' % type(el)) + + # if it's not recursive call for all,repos,groups + # break the loop and don't proceed with other changes + if recursive not in ['all', 'repos', 'groups']: + break + + def delete_permission(self, repo_group, obj, obj_type, recursive): + """ + Revokes permission for repo_group for given obj(user or users_group), + obj_type can be user or user group + + :param repo_group: + :param obj: user or user group id + :param obj_type: user or user group type + :param recursive: recurse to all children of group + """ + from rhodecode.model.repo import RepoModel + repo_group = self._get_repo_group(repo_group) + + for el in repo_group.recursive_groups_and_repos(): + # iterated obj is an instance of a repos group or repository in + # that group, recursive option can be: none, repos, groups, all + if recursive == 'all': + el = el + elif recursive == 'repos': + # skip groups, other than this one + if isinstance(el, RepoGroup) and not el == repo_group: + continue + elif recursive == 'groups': + # skip repos + if isinstance(el, Repository): + continue + else: # recursive == 'none': # DEFAULT don't apply to iterated objects + el = repo_group + # also we do a break at the end of this loop. + + if isinstance(el, RepoGroup): + if obj_type == 'user': + RepoGroupModel().revoke_user_permission(el, user=obj) + elif obj_type == 'user_group': + RepoGroupModel().revoke_user_group_permission(el, group_name=obj) + else: + raise Exception('undefined object type %s' % obj_type) + elif isinstance(el, Repository): + if obj_type == 'user': + RepoModel().revoke_user_permission(el, user=obj) + elif obj_type == 'user_group': + RepoModel().revoke_user_group_permission(el, group_name=obj) + else: + raise Exception('undefined object type %s' % obj_type) + else: + raise Exception('el should be instance of Repository or ' + 'RepositoryGroup got %s instead' % type(el)) + + # if it's not recursive call for all,repos,groups + # break the loop and don't proceed with other changes + if recursive not in ['all', 'repos', 'groups']: + break + + def grant_user_permission(self, repo_group, user, perm): + """ + Grant permission for user on given repository group, or update + existing one if found + + :param repo_group: Instance of RepoGroup, repositories_group_id, + or repositories_group name + :param user: Instance of User, user_id or username + :param perm: Instance of Permission, or permission_name + """ + + repo_group = self._get_repo_group(repo_group) + user = self._get_user(user) + permission = self._get_perm(perm) + + # check if we have that permission already + obj = self.sa.query(UserRepoGroupToPerm)\ + .filter(UserRepoGroupToPerm.user == user)\ + .filter(UserRepoGroupToPerm.group == repo_group)\ + .scalar() + if obj is None: + # create new ! + obj = UserRepoGroupToPerm() + obj.group = repo_group + obj.user = user + obj.permission = permission + self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, user, repo_group)) + return obj + + def revoke_user_permission(self, repo_group, user): + """ + Revoke permission for user on given repository group + + :param repo_group: Instance of RepoGroup, repositories_group_id, + or repositories_group name + :param user: Instance of User, user_id or username + """ + + repo_group = self._get_repo_group(repo_group) + user = self._get_user(user) + + obj = self.sa.query(UserRepoGroupToPerm)\ + .filter(UserRepoGroupToPerm.user == user)\ + .filter(UserRepoGroupToPerm.group == repo_group)\ + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm on %s on %s' % (repo_group, user)) + + def grant_user_group_permission(self, repo_group, group_name, perm): + """ + Grant permission for user group on given repository group, or update + existing one if found + + :param repo_group: Instance of RepoGroup, repositories_group_id, + or repositories_group name + :param group_name: Instance of UserGroup, users_group_id, + or user group name + :param perm: Instance of Permission, or permission_name + """ + repo_group = self._get_repo_group(repo_group) + group_name = self._get_user_group(group_name) + permission = self._get_perm(perm) + + # check if we have that permission already + obj = self.sa.query(UserGroupRepoGroupToPerm)\ + .filter(UserGroupRepoGroupToPerm.group == repo_group)\ + .filter(UserGroupRepoGroupToPerm.users_group == group_name)\ + .scalar() + + if obj is None: + # create new + obj = UserGroupRepoGroupToPerm() + + obj.group = repo_group + obj.users_group = group_name + obj.permission = permission + self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo_group)) + return obj + + def revoke_user_group_permission(self, repo_group, group_name): + """ + Revoke permission for user group on given repository group + + :param repo_group: Instance of RepoGroup, repositories_group_id, + or repositories_group name + :param group_name: Instance of UserGroup, users_group_id, + or user group name + """ + repo_group = self._get_repo_group(repo_group) + group_name = self._get_user_group(group_name) + + obj = self.sa.query(UserGroupRepoGroupToPerm)\ + .filter(UserGroupRepoGroupToPerm.group == repo_group)\ + .filter(UserGroupRepoGroupToPerm.users_group == group_name)\ + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm to %s on %s' % (repo_group, group_name)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/repo_permission.py --- a/rhodecode/model/repo_permission.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/repo_permission.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,16 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.users_group - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - repository permission model for RhodeCode - - :created_on: Oct 1, 2011 - :author: nvinot, marcink - :copyright: (C) 2011-2011 Nicolas Vinot - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -23,6 +11,15 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.users_group +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +repository permission model for RhodeCode + +:created_on: Oct 1, 2011 +:author: nvinot, marcink +""" import logging from rhodecode.model import BaseModel diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,443 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.model.user_group - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - repo group model for RhodeCode - - :created_on: Jan 25, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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 . - -import os -import logging -import traceback -import shutil -import datetime - -from rhodecode.lib.utils2 import LazyProperty - -from rhodecode.model import BaseModel -from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \ - User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository - -log = logging.getLogger(__name__) - - -class ReposGroupModel(BaseModel): - - cls = RepoGroup - - def _get_user_group(self, users_group): - return self._get_instance(UserGroup, users_group, - callback=UserGroup.get_by_group_name) - - def _get_repo_group(self, repos_group): - return self._get_instance(RepoGroup, repos_group, - callback=RepoGroup.get_by_group_name) - - @LazyProperty - def repos_path(self): - """ - Gets the repositories root path from database - """ - - q = RhodeCodeUi.get_by_key('/') - return q.ui_value - - def _create_default_perms(self, new_group): - # create default permission - default_perm = 'group.read' - def_user = User.get_default_user() - for p in def_user.user_perms: - if p.permission.permission_name.startswith('group.'): - default_perm = p.permission.permission_name - break - - repo_group_to_perm = UserRepoGroupToPerm() - repo_group_to_perm.permission = Permission.get_by_key(default_perm) - - repo_group_to_perm.group = new_group - repo_group_to_perm.user_id = def_user.user_id - return repo_group_to_perm - - def __create_group(self, group_name): - """ - makes repository group on filesystem - - :param repo_name: - :param parent_id: - """ - - create_path = os.path.join(self.repos_path, group_name) - log.debug('creating new group in %s' % create_path) - - if os.path.isdir(create_path): - raise Exception('That directory already exists !') - - os.makedirs(create_path) - - def __rename_group(self, old, new): - """ - Renames a group on filesystem - - :param group_name: - """ - - if old == new: - log.debug('skipping group rename') - return - - log.debug('renaming repository group from %s to %s' % (old, new)) - - old_path = os.path.join(self.repos_path, old) - new_path = os.path.join(self.repos_path, new) - - log.debug('renaming repos paths from %s to %s' % (old_path, new_path)) - - if os.path.isdir(new_path): - raise Exception('Was trying to rename to already ' - 'existing dir %s' % new_path) - shutil.move(old_path, new_path) - - def __delete_group(self, group, force_delete=False): - """ - Deletes a group from a filesystem - - :param group: instance of group from database - :param force_delete: use shutil rmtree to remove all objects - """ - paths = group.full_path.split(RepoGroup.url_sep()) - paths = os.sep.join(paths) - - rm_path = os.path.join(self.repos_path, paths) - log.info("Removing group %s" % (rm_path)) - # delete only if that path really exists - if os.path.isdir(rm_path): - if force_delete: - shutil.rmtree(rm_path) - else: - #archive that group` - _now = datetime.datetime.now() - _ms = str(_now.microsecond).rjust(6, '0') - _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms), - group.name) - shutil.move(rm_path, os.path.join(self.repos_path, _d)) - - def create(self, group_name, group_description, owner, parent=None, just_db=False): - try: - user = self._get_user(owner) - new_repos_group = RepoGroup() - new_repos_group.user = user - new_repos_group.group_description = group_description or group_name - new_repos_group.parent_group = self._get_repo_group(parent) - new_repos_group.group_name = new_repos_group.get_new_name(group_name) - - self.sa.add(new_repos_group) - perm_obj = self._create_default_perms(new_repos_group) - self.sa.add(perm_obj) - - #create an ADMIN permission for owner except if we're super admin, - #later owner should go into the owner field of groups - if not user.is_admin: - self.grant_user_permission(repos_group=new_repos_group, - user=owner, perm='group.admin') - - if not just_db: - # we need to flush here, in order to check if database won't - # throw any exceptions, create filesystem dirs at the very end - self.sa.flush() - self.__create_group(new_repos_group.group_name) - - return new_repos_group - except Exception: - log.error(traceback.format_exc()) - raise - - def _update_permissions(self, repos_group, perms_new=None, - perms_updates=None, recursive=False, - check_perms=True): - from rhodecode.model.repo import RepoModel - from rhodecode.lib.auth import HasUserGroupPermissionAny - - if not perms_new: - perms_new = [] - if not perms_updates: - perms_updates = [] - - def _set_perm_user(obj, user, perm): - if isinstance(obj, RepoGroup): - self.grant_user_permission( - repos_group=obj, user=user, perm=perm - ) - elif isinstance(obj, Repository): - # private repos will not allow to change the default permissions - # using recursive mode - if obj.private and user == User.DEFAULT_USER: - return - - # we set group permission but we have to switch to repo - # permission - perm = perm.replace('group.', 'repository.') - RepoModel().grant_user_permission( - repo=obj, user=user, perm=perm - ) - - def _set_perm_group(obj, users_group, perm): - if isinstance(obj, RepoGroup): - self.grant_users_group_permission( - repos_group=obj, group_name=users_group, perm=perm - ) - elif isinstance(obj, Repository): - # we set group permission but we have to switch to repo - # permission - perm = perm.replace('group.', 'repository.') - RepoModel().grant_users_group_permission( - repo=obj, group_name=users_group, perm=perm - ) - - # start updates - updates = [] - log.debug('Now updating permissions for %s in recursive mode:%s' - % (repos_group, recursive)) - - for obj in repos_group.recursive_groups_and_repos(): - #obj is an instance of a group or repositories in that group - if not recursive: - obj = repos_group - - # update permissions - for member, perm, member_type in perms_updates: - ## set for user - if member_type == 'user': - # this updates also current one if found - _set_perm_user(obj, user=member, perm=perm) - ## set for user group - else: - #check if we have permissions to alter this usergroup - req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') - if not check_perms or HasUserGroupPermissionAny(*req_perms)(member): - _set_perm_group(obj, users_group=member, perm=perm) - # set new permissions - for member, perm, member_type in perms_new: - if member_type == 'user': - _set_perm_user(obj, user=member, perm=perm) - else: - #check if we have permissions to alter this usergroup - req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin') - if not check_perms or HasUserGroupPermissionAny(*req_perms)(member): - _set_perm_group(obj, users_group=member, perm=perm) - updates.append(obj) - #if it's not recursive call - # break the loop and don't proceed with other changes - if not recursive: - break - return updates - - def update(self, repos_group, form_data): - - try: - repos_group = self._get_repo_group(repos_group) - old_path = repos_group.full_path - - # change properties - repos_group.group_description = form_data['group_description'] - repos_group.group_parent_id = form_data['group_parent_id'] - repos_group.enable_locking = form_data['enable_locking'] - - repos_group.parent_group = RepoGroup.get(form_data['group_parent_id']) - repos_group.group_name = repos_group.get_new_name(form_data['group_name']) - new_path = repos_group.full_path - self.sa.add(repos_group) - - # iterate over all members of this groups and do fixes - # set locking if given - # if obj is a repoGroup also fix the name of the group according - # to the parent - # if obj is a Repo fix it's name - # this can be potentially heavy operation - for obj in repos_group.recursive_groups_and_repos(): - #set the value from it's parent - obj.enable_locking = repos_group.enable_locking - if isinstance(obj, RepoGroup): - new_name = obj.get_new_name(obj.name) - log.debug('Fixing group %s to new name %s' \ - % (obj.group_name, new_name)) - obj.group_name = new_name - elif isinstance(obj, Repository): - # we need to get all repositories from this new group and - # rename them accordingly to new group path - new_name = obj.get_new_name(obj.just_name) - log.debug('Fixing repo %s to new name %s' \ - % (obj.repo_name, new_name)) - obj.repo_name = new_name - self.sa.add(obj) - - self.__rename_group(old_path, new_path) - - return repos_group - except Exception: - log.error(traceback.format_exc()) - raise - - def delete(self, repos_group, force_delete=False): - repos_group = self._get_repo_group(repos_group) - try: - self.sa.delete(repos_group) - self.__delete_group(repos_group, force_delete) - except Exception: - log.error('Error removing repos_group %s' % repos_group) - raise - - def delete_permission(self, repos_group, obj, obj_type, recursive): - """ - Revokes permission for repos_group for given obj(user or users_group), - obj_type can be user or user group - - :param repos_group: - :param obj: user or user group id - :param obj_type: user or user group type - :param recursive: recurse to all children of group - """ - from rhodecode.model.repo import RepoModel - repos_group = self._get_repo_group(repos_group) - - for el in repos_group.recursive_groups_and_repos(): - if not recursive: - # if we don't recurse set the permission on only the top level - # object - el = repos_group - - if isinstance(el, RepoGroup): - if obj_type == 'user': - ReposGroupModel().revoke_user_permission(el, user=obj) - elif obj_type == 'users_group': - ReposGroupModel().revoke_users_group_permission(el, group_name=obj) - else: - raise Exception('undefined object type %s' % obj_type) - elif isinstance(el, Repository): - if obj_type == 'user': - RepoModel().revoke_user_permission(el, user=obj) - elif obj_type == 'users_group': - RepoModel().revoke_users_group_permission(el, group_name=obj) - else: - raise Exception('undefined object type %s' % obj_type) - - #if it's not recursive call - # break the loop and don't proceed with other changes - if not recursive: - break - - def grant_user_permission(self, repos_group, user, perm): - """ - Grant permission for user on given repository group, or update - existing one if found - - :param repos_group: Instance of ReposGroup, repositories_group_id, - or repositories_group name - :param user: Instance of User, user_id or username - :param perm: Instance of Permission, or permission_name - """ - - repos_group = self._get_repo_group(repos_group) - user = self._get_user(user) - permission = self._get_perm(perm) - - # check if we have that permission already - obj = self.sa.query(UserRepoGroupToPerm)\ - .filter(UserRepoGroupToPerm.user == user)\ - .filter(UserRepoGroupToPerm.group == repos_group)\ - .scalar() - if obj is None: - # create new ! - obj = UserRepoGroupToPerm() - obj.group = repos_group - obj.user = user - obj.permission = permission - self.sa.add(obj) - log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group)) - - def revoke_user_permission(self, repos_group, user): - """ - Revoke permission for user on given repository group - - :param repos_group: Instance of ReposGroup, repositories_group_id, - or repositories_group name - :param user: Instance of User, user_id or username - """ - - repos_group = self._get_repo_group(repos_group) - user = self._get_user(user) - - obj = self.sa.query(UserRepoGroupToPerm)\ - .filter(UserRepoGroupToPerm.user == user)\ - .filter(UserRepoGroupToPerm.group == repos_group)\ - .scalar() - if obj: - self.sa.delete(obj) - log.debug('Revoked perm on %s on %s' % (repos_group, user)) - - def grant_users_group_permission(self, repos_group, group_name, perm): - """ - Grant permission for user group on given repository group, or update - existing one if found - - :param repos_group: Instance of ReposGroup, repositories_group_id, - or repositories_group name - :param group_name: Instance of UserGroup, users_group_id, - or user group name - :param perm: Instance of Permission, or permission_name - """ - repos_group = self._get_repo_group(repos_group) - group_name = self._get_user_group(group_name) - permission = self._get_perm(perm) - - # check if we have that permission already - obj = self.sa.query(UserGroupRepoGroupToPerm)\ - .filter(UserGroupRepoGroupToPerm.group == repos_group)\ - .filter(UserGroupRepoGroupToPerm.users_group == group_name)\ - .scalar() - - if obj is None: - # create new - obj = UserGroupRepoGroupToPerm() - - obj.group = repos_group - obj.users_group = group_name - obj.permission = permission - self.sa.add(obj) - log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group)) - - def revoke_users_group_permission(self, repos_group, group_name): - """ - Revoke permission for user group on given repository group - - :param repos_group: Instance of ReposGroup, repositories_group_id, - or repositories_group name - :param group_name: Instance of UserGroup, users_group_id, - or user group name - """ - repos_group = self._get_repo_group(repos_group) - group_name = self._get_user_group(group_name) - - obj = self.sa.query(UserGroupRepoGroupToPerm)\ - .filter(UserGroupRepoGroupToPerm.group == repos_group)\ - .filter(UserGroupRepoGroupToPerm.users_group == group_name)\ - .scalar() - if obj: - self.sa.delete(obj) - log.debug('Revoked perm to %s on %s' % (repos_group, group_name)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/scm.py --- a/rhodecode/model/scm.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/scm.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.scm - ~~~~~~~~~~~~~~~~~~~ - - Scm model for RhodeCode - - :created_on: Apr 9, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,6 +11,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.scm +~~~~~~~~~~~~~~~~~~~ + +Scm model for RhodeCode + +:created_on: Apr 9, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + from __future__ import with_statement import os import re @@ -46,7 +47,7 @@ from rhodecode.lib import helpers as h from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\ _set_extras -from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\ +from rhodecode.lib.auth import HasRepoPermissionAny, HasRepoGroupPermissionAny,\ HasUserGroupPermissionAny from rhodecode.lib.utils import get_filesystem_repos, make_ui, \ action_logger @@ -154,19 +155,16 @@ *self.perm_set)(dbr.repo_name, 'get repo check'): continue - tmp_d = {} - tmp_d['name'] = dbr.repo_name - tmp_d['name_sort'] = tmp_d['name'].lower() - tmp_d['raw_name'] = tmp_d['name'].lower() - tmp_d['description'] = dbr.description - tmp_d['description_sort'] = tmp_d['description'].lower() - tmp_d['dbrepo'] = dbr.get_dict() - tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {} + tmp_d = { + 'name': dbr.repo_name, + 'dbrepo': dbr.get_dict(), + 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {} + } yield tmp_d class _PermCheckIterator(object): - def __init__(self, obj_list, obj_attr, perm_set, perm_checker): + def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None): """ Creates iterator from given list of objects, additionally checking permission for them from perm_set var @@ -180,6 +178,7 @@ self.obj_attr = obj_attr self.perm_set = perm_set self.perm_checker = perm_checker + self.extra_kwargs = extra_kwargs or {} def __len__(self): return len(self.obj_list) @@ -191,7 +190,8 @@ for db_obj in self.obj_list: # check permission at this level name = getattr(db_obj, self.obj_attr, None) - if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__): + if not self.perm_checker(*self.perm_set)( + name, self.__class__.__name__, **self.extra_kwargs): continue yield db_obj @@ -199,35 +199,38 @@ class RepoList(_PermCheckIterator): - def __init__(self, db_repo_list, perm_set=None): + def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None): if not perm_set: perm_set = ['repository.read', 'repository.write', 'repository.admin'] super(RepoList, self).__init__(obj_list=db_repo_list, obj_attr='repo_name', perm_set=perm_set, - perm_checker=HasRepoPermissionAny) + perm_checker=HasRepoPermissionAny, + extra_kwargs=extra_kwargs) class RepoGroupList(_PermCheckIterator): - def __init__(self, db_repo_group_list, perm_set=None): + def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None): if not perm_set: perm_set = ['group.read', 'group.write', 'group.admin'] super(RepoGroupList, self).__init__(obj_list=db_repo_group_list, obj_attr='group_name', perm_set=perm_set, - perm_checker=HasReposGroupPermissionAny) + perm_checker=HasRepoGroupPermissionAny, + extra_kwargs=extra_kwargs) class UserGroupList(_PermCheckIterator): - def __init__(self, db_user_group_list, perm_set=None): + def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None): if not perm_set: perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin'] super(UserGroupList, self).__init__(obj_list=db_user_group_list, obj_attr='users_group_name', perm_set=perm_set, - perm_checker=HasUserGroupPermissionAny) + perm_checker=HasUserGroupPermissionAny, + extra_kwargs=extra_kwargs) class ScmModel(BaseModel): @@ -322,19 +325,19 @@ return repo_iter - def get_repos_groups(self, all_groups=None): + def get_repo_groups(self, all_groups=None): if all_groups is None: all_groups = RepoGroup.query()\ .filter(RepoGroup.group_parent_id == None).all() return [x for x in RepoGroupList(all_groups)] - def mark_for_invalidation(self, repo_name): + def mark_for_invalidation(self, repo_name, delete=False): """ Mark caches of this repo invalid in the database. :param repo_name: the repo for which caches should be marked invalid """ - CacheInvalidation.set_invalidate(repo_name) + CacheInvalidation.set_invalidate(repo_name, delete=delete) repo = Repository.get_by_repo_name(repo_name) if repo: repo.update_changeset_cache() @@ -432,6 +435,10 @@ fork = self.__get_repo(fork) if fork and repo.repo_id == fork.repo_id: raise Exception("Cannot set repository as fork of itself") + + if fork and repo.repo_type != fork.repo_type: + raise RepositoryError("Cannot set repository as fork of repository with other type") + repo.fork = fork self.sa.add(repo) return repo @@ -562,6 +569,43 @@ revisions=[tip.raw_id]) return tip + def _sanitize_path(self, f_path): + if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path: + raise NonRelativePathError('%s is not an relative path' % f_path) + if f_path: + f_path = os.path.normpath(f_path) + return f_path + + def get_nodes(self, repo_name, revision, root_path='/', flat=True): + """ + recursive walk in root dir and return a set of all path in that dir + based on repository walk function + + :param repo_name: name of repository + :param revision: revision for which to list nodes + :param root_path: root path to list + :param flat: return as a list, if False returns a dict with decription + + """ + _files = list() + _dirs = list() + try: + _repo = self.__get_repo(repo_name) + changeset = _repo.scm_instance.get_changeset(revision) + root_path = root_path.lstrip('/') + for topnode, dirs, files in changeset.walk(root_path): + for f in files: + _files.append(f.path if flat else {"name": f.path, + "type": "file"}) + for d in dirs: + _dirs.append(d.path if flat else {"name": d.path, + "type": "dir"}) + except RepositoryError: + log.debug(traceback.format_exc()) + raise + + return _dirs, _files + def create_nodes(self, user, repo, message, nodes, parent_cs=None, author=None, trigger_push_hook=True): """ @@ -583,10 +627,7 @@ processed_nodes = [] for f_path in nodes: - if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path: - raise NonRelativePathError('%s is not an relative path' % f_path) - if f_path: - f_path = os.path.normpath(f_path) + f_path = self._sanitize_path(f_path) content = nodes[f_path]['content'] f_path = safe_str(f_path) # decoding here will force that we have proper encoded values @@ -634,35 +675,121 @@ revisions=[tip.raw_id]) return tip - def get_nodes(self, repo_name, revision, root_path='/', flat=True): - """ - recursive walk in root dir and return a set of all path in that dir - based on repository walk function + def update_nodes(self, user, repo, message, nodes, parent_cs=None, + author=None, trigger_push_hook=True): + user = self._get_user(user) + scm_instance = repo.scm_instance_no_cache() + + message = safe_unicode(message) + commiter = user.full_contact + author = safe_unicode(author) if author else commiter + + imc_class = self._get_IMC_module(scm_instance.alias) + imc = imc_class(scm_instance) + + if not parent_cs: + parent_cs = EmptyChangeset(alias=scm_instance.alias) + + if isinstance(parent_cs, EmptyChangeset): + # EmptyChangeset means we we're editing empty repository + parents = None + else: + parents = [parent_cs] + + # add multiple nodes + for _filename, data in nodes.items(): + # new filename, can be renamed from the old one + filename = self._sanitize_path(data['filename']) + old_filename = self._sanitize_path(_filename) + content = data['content'] - :param repo_name: name of repository - :param revision: revision for which to list nodes - :param root_path: root path to list - :param flat: return as a list, if False returns a dict with decription + filenode = FileNode(old_filename, content=content) + op = data['op'] + if op == 'add': + imc.add(filenode) + elif op == 'del': + imc.remove(filenode) + elif op == 'mod': + if filename != old_filename: + #TODO: handle renames, needs vcs lib changes + imc.remove(filenode) + imc.add(FileNode(filename, content=content)) + else: + imc.change(filenode) + # commit changes + tip = imc.commit(message=message, + author=author, + parents=parents, + branch=parent_cs.branch) + + self.mark_for_invalidation(repo.repo_name) + if trigger_push_hook: + self._handle_push(scm_instance, + username=user.username, + action='push_local', + repo_name=repo.repo_name, + revisions=[tip.raw_id]) + + def delete_nodes(self, user, repo, message, nodes, parent_cs=None, + author=None, trigger_push_hook=True): """ - _files = list() - _dirs = list() - try: - _repo = self.__get_repo(repo_name) - changeset = _repo.scm_instance.get_changeset(revision) - root_path = root_path.lstrip('/') - for topnode, dirs, files in changeset.walk(root_path): - for f in files: - _files.append(f.path if flat else {"name": f.path, - "type": "file"}) - for d in dirs: - _dirs.append(d.path if flat else {"name": d.path, - "type": "dir"}) - except RepositoryError: - log.debug(traceback.format_exc()) - raise + Deletes given multiple nodes into repo + + :param user: RhodeCode User object or user_id, the commiter + :param repo: RhodeCode Repository object + :param message: commit message + :param nodes: mapping {filename:{'content':content},...} + :param parent_cs: parent changeset, can be empty than it's initial commit + :param author: author of commit, cna be different that commiter only for git + :param trigger_push_hook: trigger push hooks + + :returns: new commited changeset after deletion + """ + + user = self._get_user(user) + scm_instance = repo.scm_instance_no_cache() + + processed_nodes = [] + for f_path in nodes: + f_path = self._sanitize_path(f_path) + # content can be empty but for compatabilty it allows same dicts + # structure as add_nodes + content = nodes[f_path].get('content') + processed_nodes.append((f_path, content)) + + message = safe_unicode(message) + commiter = user.full_contact + author = safe_unicode(author) if author else commiter - return _dirs, _files + IMC = self._get_IMC_module(scm_instance.alias) + imc = IMC(scm_instance) + + if not parent_cs: + parent_cs = EmptyChangeset(alias=scm_instance.alias) + + if isinstance(parent_cs, EmptyChangeset): + # EmptyChangeset means we we're editing empty repository + parents = None + else: + parents = [parent_cs] + # add multiple nodes + for path, content in processed_nodes: + imc.remove(FileNode(path, content=content)) + + tip = imc.commit(message=message, + author=author, + parents=parents, + branch=parent_cs.branch) + + self.mark_for_invalidation(repo.repo_name) + if trigger_push_hook: + self._handle_push(scm_instance, + username=user.username, + action='push_local', + repo_name=repo.repo_name, + revisions=[tip.raw_id]) + return tip def get_unread_journal(self): return self.sa.query(UserLog).count() @@ -678,25 +805,25 @@ hist_l = [] choices = [] repo = self.__get_repo(repo) - hist_l.append(['tip', _('latest tip')]) - choices.append('tip') + hist_l.append(['rev:tip', _('latest tip')]) + choices.append('rev:tip') if not repo: return choices, hist_l repo = repo.scm_instance - branches_group = ([(k, k) for k, v in + branches_group = ([(u'branch:%s' % k, k) for k, v in repo.branches.iteritems()], _("Branches")) hist_l.append(branches_group) choices.extend([x[0] for x in branches_group[0]]) if repo.alias == 'hg': - bookmarks_group = ([(k, k) for k, v in + bookmarks_group = ([(u'book:%s' % k, k) for k, v in repo.bookmarks.iteritems()], _("Bookmarks")) hist_l.append(bookmarks_group) choices.extend([x[0] for x in bookmarks_group[0]]) - tags_group = ([(k, k) for k, v in + tags_group = ([(u'tag:%s' % k, k) for k, v in repo.tags.iteritems()], _("Tags")) hist_l.append(tags_group) choices.extend([x[0] for x in tags_group[0]]) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/user.py --- a/rhodecode/model/user.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/user.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,15 +1,4 @@ # -*- coding: utf-8 -*- -""" - rhodecode.model.user - ~~~~~~~~~~~~~~~~~~~~ - - users model for RhodeCode - - :created_on: Apr 9, 2010 - :author: marcink - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" # 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 @@ -22,24 +11,32 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +rhodecode.model.user +~~~~~~~~~~~~~~~~~~~~ + +users model for RhodeCode + +:created_on: Apr 9, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import logging import traceback -import itertools -import collections from pylons import url from pylons.i18n.translation import _ from sqlalchemy.exc import DatabaseError -from sqlalchemy.orm import joinedload + from rhodecode.lib.utils2 import safe_unicode, generate_api_key, get_current_rhodecode_user from rhodecode.lib.caching_query import FromCache from rhodecode.model import BaseModel -from rhodecode.model.db import User, Repository, Permission, \ - UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \ - Notification, RepoGroup, UserGroupRepoGroupToPerm, \ - UserEmailMap, UserIpMap, UserGroupUserGroupToPerm, UserGroup +from rhodecode.model.db import User, UserToPerm, Notification, \ + UserEmailMap, UserIpMap from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException from rhodecode.model.meta import Session @@ -47,8 +44,6 @@ log = logging.getLogger(__name__) -PERM_WEIGHTS = Permission.PERM_WEIGHTS - class UserModel(BaseModel): cls = User @@ -68,7 +63,8 @@ if case_insensitive: user = self.sa.query(User).filter(User.username.ilike(username)) else: - user = self.sa.query(User).filter(User.username == username) + user = self.sa.query(User)\ + .filter(User.username == username) if cache: user = user.options(FromCache("sql_cache_short", "get_user_%s" % username)) @@ -93,7 +89,6 @@ } # raises UserCreationError if it's not allowed check_allowed_create_user(user_data, cur_user) - from rhodecode.lib.auth import get_crypt_password try: new_user = User() @@ -114,8 +109,8 @@ raise def create_or_update(self, username, password, email, firstname='', - lastname='', active=True, admin=False, ldap_dn=None, - cur_user=None): + lastname='', active=True, admin=False, + extern_type=None, extern_name=None, cur_user=None): """ Creates a new instance if not found, or updates current one @@ -127,13 +122,14 @@ :param lastname: :param active: :param admin: - :param ldap_dn: + :param extern_name: + :param extern_type: :param cur_user: """ if not cur_user: cur_user = getattr(get_current_rhodecode_user(), 'username', None) - from rhodecode.lib.auth import get_crypt_password + from rhodecode.lib.auth import get_crypt_password, check_password from rhodecode.lib.hooks import log_create_user, check_allowed_create_user user_data = { 'username': username, 'password': password, @@ -157,15 +153,24 @@ try: new_user.username = username new_user.admin = admin - # set password only if creating an user or password is changed - if not edit or user.password != password: - new_user.password = get_crypt_password(password) if password else None - new_user.api_key = generate_api_key(username) new_user.email = email new_user.active = active - new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None + new_user.extern_name = safe_unicode(extern_name) if extern_name else None + new_user.extern_type = safe_unicode(extern_type) if extern_type else None new_user.name = firstname new_user.lastname = lastname + + if not edit: + new_user.api_key = generate_api_key(username) + + # set password only if creating an user or password is changed + password_change = new_user.password and not check_password(password, + new_user.password) + if not edit or password_change: + reason = 'new password' if edit else 'new user' + log.debug('Updating password reason=>%s' % (reason,)) + new_user.password = get_crypt_password(password) if password else None + self.sa.add(new_user) if not edit: @@ -175,116 +180,13 @@ log.error(traceback.format_exc()) raise - def create_for_container_auth(self, username, attrs, cur_user=None): - """ - Creates the given user if it's not already in the database - - :param username: - :param attrs: - :param cur_user: - """ - if not cur_user: - cur_user = getattr(get_current_rhodecode_user(), 'username', None) - if self.get_by_username(username, case_insensitive=True) is None: - # autogenerate email for container account without one - generate_email = lambda usr: '%s@container_auth.account' % usr - firstname = attrs['name'] - lastname = attrs['lastname'] - active = attrs.get('active', True) - email = attrs['email'] or generate_email(username) - - from rhodecode.lib.hooks import log_create_user, check_allowed_create_user - user_data = { - 'username': username, 'password': None, - 'email': email, 'firstname': firstname, 'lastname': lastname, - 'active': attrs.get('active', True), 'admin': False - } - # raises UserCreationError if it's not allowed - check_allowed_create_user(user_data, cur_user) - - try: - new_user = User() - new_user.username = username - new_user.password = None - new_user.api_key = generate_api_key(username) - new_user.email = email - new_user.active = active - new_user.name = firstname - new_user.lastname = lastname - - self.sa.add(new_user) - log_create_user(new_user.get_dict(), cur_user) - return new_user - except (DatabaseError,): - log.error(traceback.format_exc()) - self.sa.rollback() - raise - log.debug('User %s already exists. Skipping creation of account' - ' for container auth.', username) - return None - - def create_ldap(self, username, password, user_dn, attrs, cur_user=None): - """ - Checks if user is in database, if not creates this user marked - as ldap user - - :param username: - :param password: - :param user_dn: - :param attrs: - :param cur_user: - """ - if not cur_user: - cur_user = getattr(get_current_rhodecode_user(), 'username', None) - from rhodecode.lib.auth import get_crypt_password - log.debug('Checking for such ldap account in RhodeCode database') - if self.get_by_username(username, case_insensitive=True) is None: - # autogenerate email for container account without one - generate_email = lambda usr: '%s@ldap.account' % usr - password = get_crypt_password(password) - firstname = attrs['name'] - lastname = attrs['lastname'] - active = attrs.get('active', True) - email = attrs['email'] or generate_email(username) - - from rhodecode.lib.hooks import log_create_user, check_allowed_create_user - user_data = { - 'username': username, 'password': password, - 'email': email, 'firstname': firstname, 'lastname': lastname, - 'active': attrs.get('active', True), 'admin': False - } - # raises UserCreationError if it's not allowed - check_allowed_create_user(user_data, cur_user) - - try: - new_user = User() - username = username.lower() - # add ldap account always lowercase - new_user.username = username - new_user.password = password - new_user.api_key = generate_api_key(username) - new_user.email = email - new_user.active = active - new_user.ldap_dn = safe_unicode(user_dn) - new_user.name = firstname - new_user.lastname = lastname - self.sa.add(new_user) - - log_create_user(new_user.get_dict(), cur_user) - return new_user - except (DatabaseError,): - log.error(traceback.format_exc()) - self.sa.rollback() - raise - log.debug('this %s user exists skipping creation of ldap account', - username) - return None - def create_registration(self, form_data): from rhodecode.model.notification import NotificationModel try: form_data['admin'] = False + form_data['extern_name'] = 'rhodecode' + form_data['extern_type'] = 'rhodecode' new_user = self.create(form_data) self.sa.add(new_user) @@ -313,18 +215,19 @@ from rhodecode.lib.auth import get_crypt_password try: user = self.get(user_id, cache=False) - if user.username == 'default': + if user.username == User.DEFAULT_USER: raise DefaultUserException( - _("You can't Edit this user since it's" - " crucial for entire application")) + _("You can't Edit this user since it's " + "crucial for entire application")) for k, v in form_data.items(): if k in skip_attrs: continue if k == 'new_password' and v: user.password = get_crypt_password(v) - user.api_key = generate_api_key(user.username) else: + # old legacy thing orm models store firstname as name, + # need proper refactor to username if k == 'firstname': k = 'name' setattr(user, k, v) @@ -337,7 +240,7 @@ from rhodecode.lib.auth import get_crypt_password try: user = self._get_user(user) - if user.username == 'default': + if user.username == User.DEFAULT_USER: raise DefaultUserException( _("You can't Edit this user since it's" " crucial for entire application") @@ -346,7 +249,6 @@ for k, v in kwargs.items(): if k == 'password' and v: v = get_crypt_password(v) - user.api_key = generate_api_key(user.username) setattr(user, k, v) self.sa.add(user) @@ -361,7 +263,7 @@ user = self._get_user(user) try: - if user.username == 'default': + if user.username == User.DEFAULT_USER: raise DefaultUserException( _(u"You can't remove this user since it's" " crucial for entire application") @@ -418,7 +320,6 @@ auth.PasswordGenerator.ALPHABETS_BIG_SMALL) if user: user.password = auth.get_crypt_password(new_passwd) - user.api_key = auth.generate_api_key(user.username) Session().add(user) Session().commit() log.info('change password for %s' % user_email) @@ -441,7 +342,7 @@ return True - def fill_data(self, auth_user, user_id=None, api_key=None): + def fill_data(self, auth_user, user_id=None, api_key=None, username=None): """ Fetches auth_user by user_id,or api_key if present. Fills auth_user attributes with those taken from database. @@ -451,20 +352,25 @@ :param auth_user: instance of user to set attributes :param user_id: user id to fetch by :param api_key: api key to fetch by + :param username: username to fetch by """ - if user_id is None and api_key is None: - raise Exception('You need to pass user_id or api_key') + if user_id is None and api_key is None and username is None: + raise Exception('You need to pass user_id, api_key or username') try: - if api_key: + dbuser = None + if user_id: + dbuser = self.get(user_id) + elif api_key: dbuser = self.get_by_api_key(api_key) - else: - dbuser = self.get(user_id) + elif username: + dbuser = self.get_by_username(username) if dbuser is not None and dbuser.active: log.debug('filling %s data' % dbuser) - for k, v in dbuser.get_dict().items(): - setattr(auth_user, k, v) + for k, v in dbuser.get_dict().iteritems(): + if k not in ['api_keys', 'permissions']: + setattr(auth_user, k, v) else: return False @@ -475,296 +381,6 @@ return True - def fill_perms(self, user, explicit=True, algo='higherwin'): - """ - Fills user permission attribute with permissions taken from database - works for permissions given for repositories, and for permissions that - are granted to groups - - :param user: user instance to fill his perms - :param explicit: In case there are permissions both for user and a group - that user is part of, explicit flag will defiine if user will - explicitly override permissions from group, if it's False it will - make decision based on the algo - :param algo: algorithm to decide what permission should be choose if - it's multiple defined, eg user in two different groups. It also - decides if explicit flag is turned off how to specify the permission - for case when user is in a group + have defined separate permission - """ - RK = 'repositories' - GK = 'repositories_groups' - UK = 'user_groups' - GLOBAL = 'global' - user.permissions[RK] = {} - user.permissions[GK] = {} - user.permissions[UK] = {} - user.permissions[GLOBAL] = set() - - def _choose_perm(new_perm, cur_perm): - new_perm_val = PERM_WEIGHTS[new_perm] - cur_perm_val = PERM_WEIGHTS[cur_perm] - if algo == 'higherwin': - if new_perm_val > cur_perm_val: - return new_perm - return cur_perm - elif algo == 'lowerwin': - if new_perm_val < cur_perm_val: - return new_perm - return cur_perm - - #====================================================================== - # fetch default permissions - #====================================================================== - default_user = User.get_by_username('default', cache=True) - default_user_id = default_user.user_id - - default_repo_perms = Permission.get_default_perms(default_user_id) - default_repo_groups_perms = Permission.get_default_group_perms(default_user_id) - default_user_group_perms = Permission.get_default_user_group_perms(default_user_id) - - if user.is_admin: - #================================================================== - # admin user have all default rights for repositories - # and groups set to admin - #================================================================== - user.permissions[GLOBAL].add('hg.admin') - - # repositories - for perm in default_repo_perms: - r_k = perm.UserRepoToPerm.repository.repo_name - p = 'repository.admin' - user.permissions[RK][r_k] = p - - # repository groups - for perm in default_repo_groups_perms: - rg_k = perm.UserRepoGroupToPerm.group.group_name - p = 'group.admin' - user.permissions[GK][rg_k] = p - - # user groups - for perm in default_user_group_perms: - u_k = perm.UserUserGroupToPerm.user_group.users_group_name - p = 'usergroup.admin' - user.permissions[UK][u_k] = p - return user - - #================================================================== - # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS - #================================================================== - uid = user.user_id - - # default global permissions taken fron the default user - default_global_perms = self.sa.query(UserToPerm)\ - .filter(UserToPerm.user_id == default_user_id) - - for perm in default_global_perms: - user.permissions[GLOBAL].add(perm.permission.permission_name) - - # defaults for repositories, taken from default user - for perm in default_repo_perms: - r_k = perm.UserRepoToPerm.repository.repo_name - if perm.Repository.private and not (perm.Repository.user_id == uid): - # disable defaults for private repos, - p = 'repository.none' - elif perm.Repository.user_id == uid: - # set admin if owner - p = 'repository.admin' - else: - p = perm.Permission.permission_name - - user.permissions[RK][r_k] = p - - # defaults for repository groups taken from default user permission - # on given group - for perm in default_repo_groups_perms: - rg_k = perm.UserRepoGroupToPerm.group.group_name - p = perm.Permission.permission_name - user.permissions[GK][rg_k] = p - - # defaults for user groups taken from default user permission - # on given user group - for perm in default_user_group_perms: - u_k = perm.UserUserGroupToPerm.user_group.users_group_name - p = perm.Permission.permission_name - user.permissions[UK][u_k] = p - - #====================================================================== - # !! OVERRIDE GLOBALS !! with user permissions if any found - #====================================================================== - # those can be configured from groups or users explicitly - _configurable = set([ - 'hg.fork.none', 'hg.fork.repository', - 'hg.create.none', 'hg.create.repository', - 'hg.usergroup.create.false', 'hg.usergroup.create.true' - ]) - - # USER GROUPS comes first - # user group global permissions - user_perms_from_users_groups = self.sa.query(UserGroupToPerm)\ - .options(joinedload(UserGroupToPerm.permission))\ - .join((UserGroupMember, UserGroupToPerm.users_group_id == - UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .order_by(UserGroupToPerm.users_group_id)\ - .all() - #need to group here by groups since user can be in more than one group - _grouped = [[x, list(y)] for x, y in - itertools.groupby(user_perms_from_users_groups, - lambda x:x.users_group)] - for gr, perms in _grouped: - # since user can be in multiple groups iterate over them and - # select the lowest permissions first (more explicit) - ##TODO: do this^^ - if not gr.inherit_default_permissions: - # NEED TO IGNORE all configurable permissions and - # replace them with explicitly set - user.permissions[GLOBAL] = user.permissions[GLOBAL]\ - .difference(_configurable) - for perm in perms: - user.permissions[GLOBAL].add(perm.permission.permission_name) - - # user specific global permissions - user_perms = self.sa.query(UserToPerm)\ - .options(joinedload(UserToPerm.permission))\ - .filter(UserToPerm.user_id == uid).all() - - if not user.inherit_default_permissions: - # NEED TO IGNORE all configurable permissions and - # replace them with explicitly set - user.permissions[GLOBAL] = user.permissions[GLOBAL]\ - .difference(_configurable) - - for perm in user_perms: - user.permissions[GLOBAL].add(perm.permission.permission_name) - ## END GLOBAL PERMISSIONS - - #====================================================================== - # !! PERMISSIONS FOR REPOSITORIES !! - #====================================================================== - #====================================================================== - # check if user is part of user groups for this repository and - # fill in his permission from it. _choose_perm decides of which - # permission should be selected based on selected method - #====================================================================== - - # user group for repositories permissions - user_repo_perms_from_users_groups = \ - self.sa.query(UserGroupRepoToPerm, Permission, Repository,)\ - .join((Repository, UserGroupRepoToPerm.repository_id == - Repository.repo_id))\ - .join((Permission, UserGroupRepoToPerm.permission_id == - Permission.permission_id))\ - .join((UserGroupMember, UserGroupRepoToPerm.users_group_id == - UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .all() - - multiple_counter = collections.defaultdict(int) - for perm in user_repo_perms_from_users_groups: - r_k = perm.UserGroupRepoToPerm.repository.repo_name - multiple_counter[r_k] += 1 - p = perm.Permission.permission_name - cur_perm = user.permissions[RK][r_k] - - if perm.Repository.user_id == uid: - # set admin if owner - p = 'repository.admin' - else: - if multiple_counter[r_k] > 1: - p = _choose_perm(p, cur_perm) - user.permissions[RK][r_k] = p - - # user explicit permissions for repositories, overrides any specified - # by the group permission - user_repo_perms = Permission.get_default_perms(uid) - for perm in user_repo_perms: - r_k = perm.UserRepoToPerm.repository.repo_name - cur_perm = user.permissions[RK][r_k] - # set admin if owner - if perm.Repository.user_id == uid: - p = 'repository.admin' - else: - p = perm.Permission.permission_name - if not explicit: - p = _choose_perm(p, cur_perm) - user.permissions[RK][r_k] = p - - #====================================================================== - # !! PERMISSIONS FOR REPOSITORY GROUPS !! - #====================================================================== - #====================================================================== - # check if user is part of user groups for this repository groups and - # fill in his permission from it. _choose_perm decides of which - # permission should be selected based on selected method - #====================================================================== - # user group for repo groups permissions - user_repo_group_perms_from_users_groups = \ - self.sa.query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\ - .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((Permission, UserGroupRepoGroupToPerm.permission_id - == Permission.permission_id))\ - .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id - == UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .all() - - multiple_counter = collections.defaultdict(int) - for perm in user_repo_group_perms_from_users_groups: - g_k = perm.UserGroupRepoGroupToPerm.group.group_name - multiple_counter[g_k] += 1 - p = perm.Permission.permission_name - cur_perm = user.permissions[GK][g_k] - if multiple_counter[g_k] > 1: - p = _choose_perm(p, cur_perm) - user.permissions[GK][g_k] = p - - # user explicit permissions for repository groups - user_repo_groups_perms = Permission.get_default_group_perms(uid) - for perm in user_repo_groups_perms: - rg_k = perm.UserRepoGroupToPerm.group.group_name - p = perm.Permission.permission_name - cur_perm = user.permissions[GK][rg_k] - if not explicit: - p = _choose_perm(p, cur_perm) - user.permissions[GK][rg_k] = p - - #====================================================================== - # !! PERMISSIONS FOR USER GROUPS !! - #====================================================================== - # user group for user group permissions - user_group_user_groups_perms = \ - self.sa.query(UserGroupUserGroupToPerm, Permission, UserGroup)\ - .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id - == UserGroup.users_group_id))\ - .join((Permission, UserGroupUserGroupToPerm.permission_id - == Permission.permission_id))\ - .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id - == UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == uid)\ - .all() - - multiple_counter = collections.defaultdict(int) - for perm in user_group_user_groups_perms: - g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name - multiple_counter[g_k] += 1 - p = perm.Permission.permission_name - cur_perm = user.permissions[UK][g_k] - if multiple_counter[g_k] > 1: - p = _choose_perm(p, cur_perm) - user.permissions[UK][g_k] = p - - #user explicit permission for user groups - user_user_groups_perms = Permission.get_default_user_group_perms(uid) - for perm in user_user_groups_perms: - u_k = perm.UserUserGroupToPerm.user_group.users_group_name - p = perm.Permission.permission_name - cur_perm = user.permissions[UK][u_k] - if not explicit: - p = _choose_perm(p, cur_perm) - user.permissions[UK][u_k] = p - - return user - def has_perm(self, user, perm): perm = self._get_perm(perm) user = self._get_user(user) @@ -792,6 +408,7 @@ new.user = user new.permission = perm self.sa.add(new) + return new def revoke_perm(self, user, perm): """ diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/user_group.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/user_group.py Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,387 @@ +# -*- 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 . +""" +rhodecode.model.users_group +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +user group model for RhodeCode + +:created_on: Oct 1, 2011 +:author: nvinot, marcink +""" + + +import logging +import traceback + +from rhodecode.model import BaseModel +from rhodecode.model.db import UserGroupMember, UserGroup,\ + UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\ + UserGroupUserGroupToPerm +from rhodecode.lib.exceptions import UserGroupsAssignedException,\ + RepoGroupAssignmentError + +log = logging.getLogger(__name__) + + +class UserGroupModel(BaseModel): + + cls = UserGroup + + def _get_user_group(self, user_group): + return self._get_instance(UserGroup, user_group, + callback=UserGroup.get_by_group_name) + + def _create_default_perms(self, user_group): + # create default permission + default_perm = 'usergroup.read' + def_user = User.get_default_user() + for p in def_user.user_perms: + if p.permission.permission_name.startswith('usergroup.'): + default_perm = p.permission.permission_name + break + + user_group_to_perm = UserUserGroupToPerm() + user_group_to_perm.permission = Permission.get_by_key(default_perm) + + user_group_to_perm.user_group = user_group + user_group_to_perm.user_id = def_user.user_id + return user_group_to_perm + + def _update_permissions(self, user_group, perms_new=None, + perms_updates=None): + from rhodecode.lib.auth import HasUserGroupPermissionAny + if not perms_new: + perms_new = [] + if not perms_updates: + perms_updates = [] + + # update permissions + for member, perm, member_type in perms_updates: + if member_type == 'user': + # this updates existing one + self.grant_user_permission( + user_group=user_group, user=member, perm=perm + ) + else: + #check if we have permissions to alter this usergroup + if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write', + 'usergroup.admin')(member): + self.grant_user_group_permission( + target_user_group=user_group, user_group=member, perm=perm + ) + # set new permissions + for member, perm, member_type in perms_new: + if member_type == 'user': + self.grant_user_permission( + user_group=user_group, user=member, perm=perm + ) + else: + #check if we have permissions to alter this usergroup + if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write', + 'usergroup.admin')(member): + self.grant_user_group_permission( + target_user_group=user_group, user_group=member, perm=perm + ) + + def get(self, user_group_id, cache=False): + return UserGroup.get(user_group_id) + + def get_group(self, user_group): + return self._get_user_group(user_group) + + def get_by_name(self, name, cache=False, case_insensitive=False): + return UserGroup.get_by_group_name(name, cache, case_insensitive) + + def create(self, name, description, owner, active=True, group_data=None): + try: + new_user_group = UserGroup() + new_user_group.user = self._get_user(owner) + new_user_group.users_group_name = name + new_user_group.user_group_description = description + new_user_group.users_group_active = active + if group_data: + new_user_group.group_data = group_data + self.sa.add(new_user_group) + perm_obj = self._create_default_perms(new_user_group) + self.sa.add(perm_obj) + + self.grant_user_permission(user_group=new_user_group, + user=owner, perm='usergroup.admin') + + return new_user_group + except Exception: + log.error(traceback.format_exc()) + raise + + def update(self, user_group, form_data): + + try: + user_group = self._get_user_group(user_group) + + for k, v in form_data.items(): + if k == 'users_group_members': + user_group.members = [] + self.sa.flush() + members_list = [] + if v: + v = [v] if isinstance(v, basestring) else v + for u_id in set(v): + member = UserGroupMember(user_group.users_group_id, u_id) + members_list.append(member) + setattr(user_group, 'members', members_list) + setattr(user_group, k, v) + + self.sa.add(user_group) + except Exception: + log.error(traceback.format_exc()) + raise + + def delete(self, user_group, force=False): + """ + Deletes repository group, unless force flag is used + raises exception if there are members in that group, else deletes + group and users + + :param user_group: + :param force: + """ + user_group = self._get_user_group(user_group) + try: + # check if this group is not assigned to repo + assigned_groups = UserGroupRepoToPerm.query()\ + .filter(UserGroupRepoToPerm.users_group == user_group).all() + + if assigned_groups and not force: + raise UserGroupsAssignedException( + 'RepoGroup assigned to %s' % assigned_groups) + self.sa.delete(user_group) + except Exception: + log.error(traceback.format_exc()) + raise + + def add_user_to_group(self, user_group, user): + user_group = self._get_user_group(user_group) + user = self._get_user(user) + + for m in user_group.members: + u = m.user + if u.user_id == user.user_id: + # user already in the group, skip + return True + + try: + user_group_member = UserGroupMember() + user_group_member.user = user + user_group_member.users_group = user_group + + user_group.members.append(user_group_member) + user.group_member.append(user_group_member) + + self.sa.add(user_group_member) + return user_group_member + except Exception: + log.error(traceback.format_exc()) + raise + + def remove_user_from_group(self, user_group, user): + user_group = self._get_user_group(user_group) + user = self._get_user(user) + + user_group_member = None + for m in user_group.members: + if m.user.user_id == user.user_id: + # Found this user's membership row + user_group_member = m + break + + if user_group_member: + try: + self.sa.delete(user_group_member) + return True + except Exception: + log.error(traceback.format_exc()) + raise + else: + # User isn't in that group + return False + + def has_perm(self, user_group, perm): + user_group = self._get_user_group(user_group) + perm = self._get_perm(perm) + + return UserGroupToPerm.query()\ + .filter(UserGroupToPerm.users_group == user_group)\ + .filter(UserGroupToPerm.permission == perm).scalar() is not None + + def grant_perm(self, user_group, perm): + user_group = self._get_user_group(user_group) + perm = self._get_perm(perm) + + # if this permission is already granted skip it + _perm = UserGroupToPerm.query()\ + .filter(UserGroupToPerm.users_group == user_group)\ + .filter(UserGroupToPerm.permission == perm)\ + .scalar() + if _perm: + return + + new = UserGroupToPerm() + new.users_group = user_group + new.permission = perm + self.sa.add(new) + return new + + def revokehas_permrevoke_permgrant_perm_perm(self, user_group, perm): + user_group = self._get_user_group(user_group) + perm = self._get_perm(perm) + + obj = UserGroupToPerm.query()\ + .filter(UserGroupToPerm.users_group == user_group)\ + .filter(UserGroupToPerm.permission == perm).scalar() + if obj: + self.sa.delete(obj) + + def grant_user_permission(self, user_group, user, perm): + """ + Grant permission for user on given user group, or update + existing one if found + + :param user_group: Instance of UserGroup, users_group_id, + or users_group_name + :param user: Instance of User, user_id or username + :param perm: Instance of Permission, or permission_name + """ + + user_group = self._get_user_group(user_group) + user = self._get_user(user) + permission = self._get_perm(perm) + + # check if we have that permission already + obj = self.sa.query(UserUserGroupToPerm)\ + .filter(UserUserGroupToPerm.user == user)\ + .filter(UserUserGroupToPerm.user_group == user_group)\ + .scalar() + if obj is None: + # create new ! + obj = UserUserGroupToPerm() + obj.user_group = user_group + obj.user = user + obj.permission = permission + self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, user, user_group)) + return obj + + def revoke_user_permission(self, user_group, user): + """ + Revoke permission for user on given repository group + + :param user_group: Instance of RepoGroup, repositories_group_id, + or repositories_group name + :param user: Instance of User, user_id or username + """ + + user_group = self._get_user_group(user_group) + user = self._get_user(user) + + obj = self.sa.query(UserUserGroupToPerm)\ + .filter(UserUserGroupToPerm.user == user)\ + .filter(UserUserGroupToPerm.user_group == user_group)\ + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm on %s on %s' % (user_group, user)) + + def grant_user_group_permission(self, target_user_group, user_group, perm): + """ + Grant user group permission for given target_user_group + + :param target_user_group: + :param user_group: + :param perm: + """ + target_user_group = self._get_user_group(target_user_group) + user_group = self._get_user_group(user_group) + permission = self._get_perm(perm) + # forbid assigning same user group to itself + if target_user_group == user_group: + raise RepoGroupAssignmentError('target repo:%s cannot be ' + 'assigned to itself' % target_user_group) + + # check if we have that permission already + obj = self.sa.query(UserGroupUserGroupToPerm)\ + .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\ + .filter(UserGroupUserGroupToPerm.user_group == user_group)\ + .scalar() + if obj is None: + # create new ! + obj = UserGroupUserGroupToPerm() + obj.user_group = user_group + obj.target_user_group = target_user_group + obj.permission = permission + self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, target_user_group, user_group)) + return obj + + def revoke_user_group_permission(self, target_user_group, user_group): + """ + Revoke user group permission for given target_user_group + + :param target_user_group: + :param user_group: + """ + target_user_group = self._get_user_group(target_user_group) + user_group = self._get_user_group(user_group) + + obj = self.sa.query(UserGroupUserGroupToPerm)\ + .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\ + .filter(UserGroupUserGroupToPerm.user_group == user_group)\ + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm on %s on %s' % (target_user_group, user_group)) + + def enforce_groups(self, user, groups, extern_type=None): + user = self._get_user(user) + log.debug('Enforcing groups %s on user %s' % (user, groups)) + current_groups = user.group_member + # find the external created groups + externals = [x.users_group for x in current_groups + if 'extern_type' in x.users_group.group_data] + + # calculate from what groups user should be removed + # externals that are not in groups + for gr in externals: + if gr.users_group_name not in groups: + log.debug('Removing user %s from user group %s' % (user, gr)) + self.remove_user_from_group(gr, user) + + # now we calculate in which groups user should be == groups params + owner = User.get_first_admin().username + for gr in set(groups): + existing_group = UserGroup.get_by_group_name(gr) + if not existing_group: + desc = 'Automatically created from plugin:%s' % extern_type + # we use first admin account to set the owner of the group + existing_group = UserGroupModel().create(gr, desc, owner, + group_data={'extern_type': extern_type}) + + # we can only add users to special groups created via plugins + managed = 'extern_type' in existing_group.group_data + if managed: + log.debug('Adding user %s to user group %s' % (user, gr)) + UserGroupModel().add_user_to_group(existing_group, user) + else: + log.debug('Skipping addition to group %s since it is ' + 'not managed by auth plugins' % gr) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/users_group.py --- a/rhodecode/model/users_group.py Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,350 +0,0 @@ -# -*- coding: utf-8 -*- -""" - rhodecode.model.users_group - ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - user group model for RhodeCode - - :created_on: Oct 1, 2011 - :author: nvinot - :copyright: (C) 2011-2011 Nicolas Vinot - :copyright: (C) 2010-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# 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 . - -import logging -import traceback - -from rhodecode.model import BaseModel -from rhodecode.model.db import UserGroupMember, UserGroup,\ - UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\ - UserGroupUserGroupToPerm -from rhodecode.lib.exceptions import UserGroupsAssignedException,\ - RepoGroupAssignmentError - -log = logging.getLogger(__name__) - - -class UserGroupModel(BaseModel): - - cls = UserGroup - - def _get_user_group(self, users_group): - return self._get_instance(UserGroup, users_group, - callback=UserGroup.get_by_group_name) - - def _create_default_perms(self, user_group): - # create default permission - default_perm = 'usergroup.read' - def_user = User.get_default_user() - for p in def_user.user_perms: - if p.permission.permission_name.startswith('usergroup.'): - default_perm = p.permission.permission_name - break - - user_group_to_perm = UserUserGroupToPerm() - user_group_to_perm.permission = Permission.get_by_key(default_perm) - - user_group_to_perm.user_group = user_group - user_group_to_perm.user_id = def_user.user_id - return user_group_to_perm - - def _update_permissions(self, user_group, perms_new=None, - perms_updates=None): - from rhodecode.lib.auth import HasUserGroupPermissionAny - if not perms_new: - perms_new = [] - if not perms_updates: - perms_updates = [] - - # update permissions - for member, perm, member_type in perms_updates: - if member_type == 'user': - # this updates existing one - self.grant_user_permission( - user_group=user_group, user=member, perm=perm - ) - else: - #check if we have permissions to alter this usergroup - if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write', - 'usergroup.admin')(member): - self.grant_users_group_permission( - target_user_group=user_group, user_group=member, perm=perm - ) - # set new permissions - for member, perm, member_type in perms_new: - if member_type == 'user': - self.grant_user_permission( - user_group=user_group, user=member, perm=perm - ) - else: - #check if we have permissions to alter this usergroup - if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write', - 'usergroup.admin')(member): - self.grant_users_group_permission( - target_user_group=user_group, user_group=member, perm=perm - ) - - def get(self, users_group_id, cache=False): - return UserGroup.get(users_group_id) - - def get_group(self, users_group): - return self._get_user_group(users_group) - - def get_by_name(self, name, cache=False, case_insensitive=False): - return UserGroup.get_by_group_name(name, cache, case_insensitive) - - def create(self, name, owner, active=True): - try: - new_user_group = UserGroup() - new_user_group.user = self._get_user(owner) - new_user_group.users_group_name = name - new_user_group.users_group_active = active - self.sa.add(new_user_group) - perm_obj = self._create_default_perms(new_user_group) - self.sa.add(perm_obj) - - self.grant_user_permission(user_group=new_user_group, - user=owner, perm='usergroup.admin') - - return new_user_group - except Exception: - log.error(traceback.format_exc()) - raise - - def update(self, users_group, form_data): - - try: - users_group = self._get_user_group(users_group) - - for k, v in form_data.items(): - if k == 'users_group_members': - users_group.members = [] - self.sa.flush() - members_list = [] - if v: - v = [v] if isinstance(v, basestring) else v - for u_id in set(v): - member = UserGroupMember(users_group.users_group_id, u_id) - members_list.append(member) - setattr(users_group, 'members', members_list) - setattr(users_group, k, v) - - self.sa.add(users_group) - except Exception: - log.error(traceback.format_exc()) - raise - - def delete(self, users_group, force=False): - """ - Deletes repository group, unless force flag is used - raises exception if there are members in that group, else deletes - group and users - - :param users_group: - :param force: - """ - try: - users_group = self._get_user_group(users_group) - - # check if this group is not assigned to repo - assigned_groups = UserGroupRepoToPerm.query()\ - .filter(UserGroupRepoToPerm.users_group == users_group).all() - - if assigned_groups and not force: - raise UserGroupsAssignedException('RepoGroup assigned to %s' % - assigned_groups) - - self.sa.delete(users_group) - except Exception: - log.error(traceback.format_exc()) - raise - - def add_user_to_group(self, users_group, user): - users_group = self._get_user_group(users_group) - user = self._get_user(user) - - for m in users_group.members: - u = m.user - if u.user_id == user.user_id: - return True - - try: - users_group_member = UserGroupMember() - users_group_member.user = user - users_group_member.users_group = users_group - - users_group.members.append(users_group_member) - user.group_member.append(users_group_member) - - self.sa.add(users_group_member) - return users_group_member - except Exception: - log.error(traceback.format_exc()) - raise - - def remove_user_from_group(self, users_group, user): - users_group = self._get_user_group(users_group) - user = self._get_user(user) - - users_group_member = None - for m in users_group.members: - if m.user.user_id == user.user_id: - # Found this user's membership row - users_group_member = m - break - - if users_group_member: - try: - self.sa.delete(users_group_member) - return True - except Exception: - log.error(traceback.format_exc()) - raise - else: - # User isn't in that group - return False - - def has_perm(self, users_group, perm): - users_group = self._get_user_group(users_group) - perm = self._get_perm(perm) - - return UserGroupToPerm.query()\ - .filter(UserGroupToPerm.users_group == users_group)\ - .filter(UserGroupToPerm.permission == perm).scalar() is not None - - def grant_perm(self, users_group, perm): - users_group = self._get_user_group(users_group) - perm = self._get_perm(perm) - - # if this permission is already granted skip it - _perm = UserGroupToPerm.query()\ - .filter(UserGroupToPerm.users_group == users_group)\ - .filter(UserGroupToPerm.permission == perm)\ - .scalar() - if _perm: - return - - new = UserGroupToPerm() - new.users_group = users_group - new.permission = perm - self.sa.add(new) - - def revoke_perm(self, users_group, perm): - users_group = self._get_user_group(users_group) - perm = self._get_perm(perm) - - obj = UserGroupToPerm.query()\ - .filter(UserGroupToPerm.users_group == users_group)\ - .filter(UserGroupToPerm.permission == perm).scalar() - if obj: - self.sa.delete(obj) - - def grant_user_permission(self, user_group, user, perm): - """ - Grant permission for user on given user group, or update - existing one if found - - :param user_group: Instance of UserGroup, users_group_id, - or users_group_name - :param user: Instance of User, user_id or username - :param perm: Instance of Permission, or permission_name - """ - - user_group = self._get_user_group(user_group) - user = self._get_user(user) - permission = self._get_perm(perm) - - # check if we have that permission already - obj = self.sa.query(UserUserGroupToPerm)\ - .filter(UserUserGroupToPerm.user == user)\ - .filter(UserUserGroupToPerm.user_group == user_group)\ - .scalar() - if obj is None: - # create new ! - obj = UserUserGroupToPerm() - obj.user_group = user_group - obj.user = user - obj.permission = permission - self.sa.add(obj) - log.debug('Granted perm %s to %s on %s' % (perm, user, user_group)) - - def revoke_user_permission(self, user_group, user): - """ - Revoke permission for user on given repository group - - :param user_group: Instance of ReposGroup, repositories_group_id, - or repositories_group name - :param user: Instance of User, user_id or username - """ - - user_group = self._get_user_group(user_group) - user = self._get_user(user) - - obj = self.sa.query(UserUserGroupToPerm)\ - .filter(UserUserGroupToPerm.user == user)\ - .filter(UserUserGroupToPerm.user_group == user_group)\ - .scalar() - if obj: - self.sa.delete(obj) - log.debug('Revoked perm on %s on %s' % (user_group, user)) - - def grant_users_group_permission(self, target_user_group, user_group, perm): - """ - Grant user group permission for given target_user_group - - :param target_user_group: - :param user_group: - :param perm: - """ - target_user_group = self._get_user_group(target_user_group) - user_group = self._get_user_group(user_group) - permission = self._get_perm(perm) - # forbid assigning same user group to itself - if target_user_group == user_group: - raise RepoGroupAssignmentError('target repo:%s cannot be ' - 'assigned to itself' % target_user_group) - - # check if we have that permission already - obj = self.sa.query(UserGroupUserGroupToPerm)\ - .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\ - .filter(UserGroupUserGroupToPerm.user_group == user_group)\ - .scalar() - if obj is None: - # create new ! - obj = UserGroupUserGroupToPerm() - obj.user_group = user_group - obj.target_user_group = target_user_group - obj.permission = permission - self.sa.add(obj) - log.debug('Granted perm %s to %s on %s' % (perm, target_user_group, user_group)) - - def revoke_users_group_permission(self, target_user_group, user_group): - """ - Revoke user group permission for given target_user_group - - :param target_user_group: - :param user_group: - """ - target_user_group = self._get_user_group(target_user_group) - user_group = self._get_user_group(user_group) - - obj = self.sa.query(UserGroupUserGroupToPerm)\ - .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\ - .filter(UserGroupUserGroupToPerm.user_group == user_group)\ - .scalar() - if obj: - self.sa.delete(obj) - log.debug('Revoked perm on %s on %s' % (target_user_group, user_group)) diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/model/validators.py --- a/rhodecode/model/validators.py Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/model/validators.py Wed Jul 02 19:03:13 2014 -0400 @@ -1,6 +1,20 @@ +# -*- 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 . """ Set of generic validators """ + import os import re import formencode @@ -16,12 +30,12 @@ from rhodecode.lib.compat import OrderedSet from rhodecode.lib import ipaddr from rhodecode.lib.utils import repo_name_slug -from rhodecode.lib.utils2 import safe_int, str2bool +from rhodecode.lib.utils2 import safe_int, str2bool, aslist from rhodecode.model.db import RepoGroup, Repository, UserGroup, User,\ ChangesetStatus from rhodecode.lib.exceptions import LdapImportError from rhodecode.config.routing import ADMIN_PREFIX -from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny +from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny # silence warnings and pylint UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \ @@ -29,30 +43,10 @@ log = logging.getLogger(__name__) - -class UniqueList(formencode.FancyValidator): - """ - Unique List ! - """ - messages = dict( - empty=_('Value cannot be an empty list'), - missing_value=_('Value cannot be an empty list'), - ) +class _Missing(object): + pass - def _to_python(self, value, state): - if isinstance(value, list): - return value - elif isinstance(value, set): - return list(value) - elif isinstance(value, tuple): - return list(value) - elif value is None: - return [] - else: - return [value] - - def empty_value(self, value): - return [] +Missing = _Missing() class StateObj(object): @@ -79,6 +73,47 @@ return self.message(key, state, **kwargs) +def UniqueList(): + class _UniqueList(formencode.FancyValidator): + """ + Unique List ! + """ + messages = dict( + empty=_('Value cannot be an empty list'), + missing_value=_('Value cannot be an empty list'), + ) + + def _to_python(self, value, state): + def make_unique(value): + seen = [] + return [c for c in value if not (c in seen or seen.append(c))] + + if isinstance(value, list): + return make_unique(value) + elif isinstance(value, set): + return make_unique(list(value)) + elif isinstance(value, tuple): + return make_unique(list(value)) + elif value is None: + return [] + else: + return [value] + + def empty_value(self, value): + return [] + + return _UniqueList + + +def UniqueListFromString(): + class _UniqueListFromString(UniqueList()): + def _to_python(self, value, state): + if isinstance(value, basestring): + value = aslist(value, ',') + return super(_UniqueListFromString, self)._to_python(value, state) + return _UniqueListFromString + + def ValidUsername(edit=False, old_data={}): class _validator(formencode.validators.FancyValidator): messages = { @@ -111,6 +146,12 @@ return _validator +def ValidRegex(msg=None): + class _validator(formencode.validators.Regex): + messages = dict(invalid=msg or _('The input is not valid')) + return _validator + + def ValidRepoUser(): class _validator(formencode.validators.FancyValidator): messages = { @@ -171,7 +212,7 @@ return _validator -def ValidReposGroup(edit=False, old_data={}): +def ValidRepoGroup(edit=False, old_data={}): class _validator(formencode.validators.FancyValidator): messages = { 'group_parent_id': _(u'Cannot assign this group as parent'), @@ -247,7 +288,23 @@ return _validator -def ValidPasswordsMatch(): +def ValidOldPassword(username): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_password': _(u'Invalid old password') + } + + def validate_python(self, value, state): + from rhodecode.lib import auth_modules + if not auth_modules.authenticate(username, value, ''): + msg = M(self, 'invalid_password', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(current_password=msg) + ) + return _validator + + +def ValidPasswordsMatch(passwd='new_password', passwd_confirmation='password_confirmation'): class _validator(formencode.validators.FancyValidator): messages = { 'password_mismatch': _(u'Passwords do not match'), @@ -255,11 +312,11 @@ def validate_python(self, value, state): - pass_val = value.get('password') or value.get('new_password') - if pass_val != value['password_confirmation']: + pass_val = value.get('password') or value.get(passwd) + if pass_val != value[passwd_confirmation]: msg = M(self, 'password_mismatch', state) raise formencode.Invalid(msg, value, state, - error_dict=dict(password_confirmation=msg) + error_dict={passwd:msg, passwd_confirmation: msg} ) return _validator @@ -273,12 +330,12 @@ } def validate_python(self, value, state): - from rhodecode.lib.auth import authenticate + from rhodecode.lib import auth_modules password = value['password'] username = value['username'] - if not authenticate(username, password): + if not auth_modules.authenticate(username, password): user = User.get_by_username(username) if user and not user.active: log.warning('user %s is disabled' % username) @@ -403,18 +460,16 @@ def ValidCloneUri(): from rhodecode.lib.utils import make_ui - def url_handler(repo_type, url, ui=None): + def url_handler(repo_type, url, ui): if repo_type == 'hg': from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository - from rhodecode.lib.vcs.utils.hgcompat import httppeer if url.startswith('http'): - ## initially check if it's at least the proper URL - ## or does it pass basic auth - MercurialRepository._check_url(url) - httppeer(ui, url)._capabilities() + # initially check if it's at least the proper URL + # or does it pass basic auth + MercurialRepository._check_url(url, ui) elif url.startswith('svn+http'): from hgsubversion.svnrepo import svnremoterepo - svnremoterepo(ui, url).capabilities + svnremoterepo(ui, url).svn.uuid elif url.startswith('git+http'): raise NotImplementedError() else: @@ -423,8 +478,8 @@ elif repo_type == 'git': from rhodecode.lib.vcs.backends.git.repository import GitRepository if url.startswith('http'): - ## initially check if it's at least the proper URL - ## or does it pass basic auth + # initially check if it's at least the proper URL + # or does it pass basic auth GitRepository._check_url(url) elif url.startswith('svn+http'): raise NotImplementedError() @@ -491,11 +546,23 @@ def validate_python(self, value, state): gr = RepoGroup.get(value) gr_name = gr.group_name if gr else None # None means ROOT location - val = HasReposGroupPermissionAny('group.write', 'group.admin') + # 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')(gr_name, + 'can write into group validator') + group_write = HasRepoGroupPermissionAny('group.write')(gr_name, + 'can write into group validator') + forbidden = not (group_admin or (group_write and create_on_write)) can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository') - forbidden = not val(gr_name, 'can write into group validator') - value_changed = True # old_data['repo_group'].get('group_id') != safe_int(value) - if value_changed: # do check if we changed the value + gid = (old_data['repo_group'].get('group_id') + if (old_data and 'repo_group' in old_data) else None) + value_changed = gid != safe_int(value) + new = not old_data + # do check if we changed the value, there's a case that someone got + # revoked write permissions to a repository, he still created, we + # don't need to check permission if he didn't change the value of + # groups in form box + if value_changed or new: #parent group need to be existing if gr and forbidden: msg = M(self, 'permission_denied', state) @@ -534,7 +601,7 @@ return forbidden_in_root = gr is None and not can_create_in_root - val = HasReposGroupPermissionAny('group.admin') + val = HasRepoGroupPermissionAny('group.admin') forbidden = not val(gr_name, 'can create group validator') if forbidden_in_root or forbidden: msg = M(self, 'permission_denied', state) @@ -592,7 +659,7 @@ t = {'u': 'user', 'g': 'users_group' }[k[0]] - if member == 'default': + if member == User.DEFAULT_USER: if str2bool(value.get('repo_private')): # set none for default when updating to # private repo protects agains form manipulation @@ -718,7 +785,7 @@ def AttrLoginValidator(): - class _validator(formencode.validators.FancyValidator): + class _validator(formencode.validators.UnicodeString): messages = { 'invalid_cn': _(u'The LDAP Login attribute of the CN must be specified - ' @@ -825,3 +892,33 @@ raise formencode.Invalid(self.message('badPath', state), value, state) return _validator + + +def ValidAuthPlugins(): + class _validator(formencode.validators.FancyValidator): + messages = dict( + import_duplicate=_('Plugins %(loaded)s and %(next_to_load)s both export the same name') + ) + + def _to_python(self, value, state): + # filter empty values + return filter(lambda s: s not in [None, ''], value) + + def validate_python(self, value, state): + from rhodecode.lib import auth_modules + module_list = value + unique_names = {} + try: + for module in module_list: + plugin = auth_modules.loadplugin(module) + plugin_name = plugin.name + if plugin_name in unique_names: + msg = M(self, 'import_duplicate', state, + loaded=unique_names[plugin_name], + next_to_load=plugin_name) + raise formencode.Invalid(msg, value, state) + unique_names[plugin_name] = plugin + except (ImportError, AttributeError, TypeError), e: + raise formencode.Invalid(str(e), value, state) + + return _validator diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/admin.html --- a/rhodecode/templates/admin/admin.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/admin.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,14 +2,17 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Admin journal')} · ${c.rhodecode_name} + ${_('Admin journal')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()">
? - + ${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/auth/auth_settings.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/auth/auth_settings.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,137 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Authentication Settings')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} + » + ${_('Authentication')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} +
+ ${h.form(url('auth_settings'))} +
+ + ## enabled auth plugins +

${_('Authentication Plugins')}

+
+
+
+
${h.text("auth_plugins", class_='large')} + ${_('Comma separated list of plugins. Order of plugins is also order in which RhodeCode will try to authenticate user')} +
${_('Available built-in plugins')}
+
    + %for plugin_path in c.available_plugins: +
  • +
    + + ${_('enabled') if plugin_path in c.enabled_plugins else _('disabled')}${plugin_path} +
    +
  • + %endfor +
+
+
+
+ ${h.submit('save',_('Save'),class_="btn")} +
+
+ + %for cnt, module in enumerate(c.auth_plugins): + <% pluginName = c.auth_plugins_shortnames[module] %> +

${_('Plugin')}: ${pluginName}

+
+ ## autoform generation, based on plugin definition from it's settings + %for setting in c.plugin_settings[module]: + <% fullsetting = "auth_%s_%s" % (pluginName, setting["name"]) %> + <% displayname = (setting["formname"] if ("formname" in setting) else setting["name"]) %> + %if setting["type"] == "password": +
+
+
+ ${h.password(fullsetting,class_='small')} + ${setting["description"]} +
+
+ %elif setting["type"] in ["string", "int"]: +
+
+
+ ${h.text(fullsetting,class_='small')} + ${setting["description"]} +
+
+ %elif setting["type"] == "bool": +
+
+
+
${h.checkbox(fullsetting,True,class_='small')}
+ ${setting["description"]} +
+
+ %elif setting["type"] == "select": +
+
+
+ ${h.select(fullsetting,setting['values'][0],setting['values'],class_='small')} + ${setting["description"]} +
+
+ %else: +
+
+
This field is of type ${setting['type']}, which cannot be displayed. Must be one of [string|int|bool|select].
+ ${setting["description"]} +
+ %endif + %endfor +
+ %endfor +
+ ${h.end_form()} +
+ + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/defaults/defaults.html --- a/rhodecode/templates/admin/defaults/defaults.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/defaults/defaults.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Repositories defaults')} · ${c.rhodecode_name} + ${_('Repositories defaults')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -81,7 +84,7 @@
- ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.submit('save',_('Save'),class_="btn")}
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/gists/edit.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/gists/edit.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,176 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Edit Gist')} · ${c.gist.gist_access_id} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="js_extra()"> + + + + + +<%def name="css_extra()"> + + + +<%def name="breadcrumbs_links()"> + ${_('Edit Gist')} · ${c.gist.gist_access_id} + + +<%def name="page_nav()"> + ${self.menu('gists')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} +
+ +
+ + +
+ ${h.form(h.url('edit_gist', gist_id=c.gist.gist_access_id), method='post', id='eform')} +
+
+ gravatar +
+ + +
+ + ${h.select('lifetime', '0', c.lifetime_options)} + + %if c.gist.gist_expires == -1: + ${_('Expires')}: ${_('never')} + %else: + ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))} + %endif + +
+
+ + % for cnt, file in enumerate(c.files): +
+
+ + + + +
+
+

+                        
+                    
+
+ + ## dynamic edit box. + + + %endfor + +
+ ${h.submit('update',_('Update Gist'),class_="btn btn-mini btn-success")} + ${_('Cancel')} +
+ ${h.end_form()} + +
+
+ +
+ diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/gists/index.html --- a/rhodecode/templates/admin/gists/index.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/gists/index.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,16 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Gists')} · ${c.rhodecode_name} + %if c.show_private: + ${_('Private Gists for user %s') % c.rhodecode_user.username} + %elif c.show_public: + ${_('Public Gists for user %s') % c.rhodecode_user.username} + %else: + ${_('Public Gists')} + %endif + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -28,7 +37,7 @@ %if c.rhodecode_user.username != 'default': %endif @@ -42,7 +51,7 @@
${h.person(gist.owner.full_contact)} / - gist:${gist.gist_access_id} + gist: ${gist.gist_access_id}
${_('Created')} ${h.age(gist.created_on)} / @@ -61,7 +70,7 @@
- ${c.gists_pager.pager('$link_previous ~2~ $link_next')} + ${c.gists_pager.pager('$link_previous ~2~ $link_next', **request.GET.mixed())}
%else: diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/gists/new.html --- a/rhodecode/templates/admin/gists/new.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/gists/new.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('New gist')} · ${c.rhodecode_name} + ${_('New Gist')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="js_extra()"> @@ -16,7 +19,7 @@ <%def name="breadcrumbs_links()"> - ${_('New gist')} + ${_('New Gist')} <%def name="page_nav()"> @@ -45,9 +48,7 @@
- ## - ${h.text('filename', size=30, placeholder='gistfile1.txt')} - ## + ${h.text('filename', size=30, placeholder=_('name this file...'))} ${h.select('mimetype','plain',[('plain',_('plain'))])}
@@ -56,9 +57,9 @@
- ${h.submit('private',_('Create private gist'),class_="ui-btn yellow")} - ${h.submit('public',_('Create public gist'),class_="ui-btn")} - ${h.reset('reset',_('Reset'),class_="ui-btn")} + ${h.submit('private',_('Create Private Gist'),class_="btn btn-mini btn-success")} + ${h.submit('public',_('Create Public Gist'),class_="btn btn-mini")} + ${h.reset('reset',_('Reset'),class_="btn btn-mini")}
${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/gists/show.html --- a/rhodecode/templates/admin/gists/show.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/gists/show.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,11 +2,15 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('gist')}:${c.gist.gist_access_id} · ${c.rhodecode_name} + ${_('Gist')} · ${c.gist.gist_access_id} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> - ${_('Gist')} · gist:${c.gist.gist_access_id} + ${_('Gist')} · ${c.gist.gist_access_id} + / ${_('URL')}: ${c.gist.gist_url()} <%def name="page_nav()"> @@ -21,7 +25,7 @@ %if c.rhodecode_user.username != 'default': %endif @@ -33,34 +37,35 @@
%if c.gist.gist_type == 'public': -
${_('Public gist')}
+
${_('Public Gist')}
%else: -
${_('Private gist')}
+
${_('Private Gist')}
%endif
-
+
+ ${c.gist.gist_description} +
+
%if c.gist.gist_expires == -1: ${_('Expires')}: ${_('never')} %else: ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))} %endif
-
- ${c.gist.gist_description} -
+ %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id: -
+
${h.form(url('gist', gist_id=c.gist.gist_id),method='delete')} - ${h.submit('remove_gist', _('Delete'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this gist')+"');")} + ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")} ${h.end_form()}
%endif
## only owner should see that - ##%if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id: - ##${h.link_to(_('Edit'),h.url(''),class_="ui-btn")} - ##%endif - ${h.link_to(_('Show as raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="ui-btn")} + %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id: + ${h.link_to(_('Edit'),h.url('edit_gist', gist_id=c.gist.gist_access_id),class_="btn btn-mini")} + %endif + ${h.link_to(_('Show as Raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="btn btn-mini")}
@@ -80,8 +85,8 @@
ยถ ${file.path} -
- ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=file.path),class_="ui-btn")} +
+ ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=file.path),class_="btn btn-mini")}
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/ldap/ldap.html --- a/rhodecode/templates/admin/ldap/ldap.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('LDAP administration')} · ${c.rhodecode_name} - - -<%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} - » - ${_('LDAP')} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} -
- ${h.form(url('ldap_settings'))} -
-
- -

${_('Connection settings')}

-
-
-
${h.checkbox('ldap_active',True,class_='small')}
-
-
-
-
${h.text('ldap_host',class_='small')}
-
-
-
-
${h.text('ldap_port',class_='small')}
-
-
-
-
${h.text('ldap_dn_user',class_='small')}
-
-
-
-
${h.password('ldap_dn_pass',class_='small')}
-
-
-
-
${h.select('ldap_tls_kind',c.tls_kind_cur,c.tls_kind_choices,class_='small')}
-
-
-
-
${h.select('ldap_tls_reqcert',c.tls_reqcert_cur,c.tls_reqcert_choices,class_='small')}
-
-

${_('Search settings')}

-
-
-
${h.text('ldap_base_dn',class_='small')}
-
-
-
-
${h.text('ldap_filter',class_='small')}
-
-
-
-
${h.select('ldap_search_scope',c.search_scope_cur,c.search_scope_choices,class_='small')}
-
-

${_('Attribute mappings')}

-
-
-
${h.text('ldap_attr_login',class_='small')}
-
-
-
-
${h.text('ldap_attr_firstname',class_='small')}
-
-
-
-
${h.text('ldap_attr_lastname',class_='small')}
-
-
-
-
${h.text('ldap_attr_email',class_='small')}
-
- -
- ${h.submit('save',_('Save'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,54 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('My account')} ${c.rhodecode_user.username} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + ${_('My Account')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+
+ ${self.breadcrumbs()} +
+ + ##main + + +
+ <%include file="/admin/my_account/my_account_${c.active}.html"/> +
+
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_api_keys.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_api_keys.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,83 @@ +
+ + + + + + + + %if c.user_api_keys: + %for api_key in c.user_api_keys: + + + + + + + %endfor + %else: + + %endif +
${c.user.api_key}
+ ${_('Built-in')} + ${_('expires')}: ${_('never')} + ${h.form(url('my_account_api_keys'),method='delete')} + ${h.hidden('del_api_key',c.user.api_key)} + ${h.hidden('del_api_key_builtin',1)} + + ${h.end_form()} +
${api_key.api_key}
${api_key.description} + %if api_key.expires == -1: + ${_('expires')}: ${_('never')} + %else: + %if api_key.expired: + ${_('expired')}: ${h.age(h.time_to_datetime(api_key.expires))} + %else: + ${_('expires')}: ${h.age(h.time_to_datetime(api_key.expires))} + %endif + %endif + + ${h.form(url('my_account_api_keys'),method='delete')} + ${h.hidden('del_api_key',api_key.api_key)} + + ${h.end_form()} +
${_('No additional api keys specified')}
+
+ +
+ ${h.form(url('my_account_api_keys'), method='post')} +
+ +
+
+
+ +
+
+ ${h.text('description', class_='medium', placeholder=_('Description'))} + ${h.select('lifetime', '', c.lifetime_options)} +
+
+
+ ${h.submit('save',_('Add'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} +
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_emails.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_emails.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,51 @@ +
+ + + + + + + %if c.user_email_map: + %for em in c.user_email_map: + + + + + + %endfor + %else: + + %endif +
gravatar
+ ${_('Primary')} +
gravatar
+ ${h.form(url('my_account_emails'),method='delete')} + ${h.hidden('del_email_id',em.email_id)} + + ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id, + class_="action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")} + ${h.end_form()} +
${_('No additional emails specified')}
+
+ +
+ ${h.form(url('my_account_emails'), method='post')} +
+ +
+
+
+ +
+
+ ${h.text('new_email', class_='medium')} +
+
+
+ ${h.submit('save',_('Add'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} +
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_password.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_password.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,38 @@ +
${_('Change your account password')}
+${h.form(url('my_account_password'), method='post')} +
+
+
+
+ +
+
+ ${h.password('current_password',class_='medium',autocomplete="off")} +
+
+ +
+
+ +
+
+ ${h.password('new_password',class_='medium', autocomplete="off")} +
+
+ +
+
+ +
+
+ ${h.password('new_password_confirmation',class_='medium', autocomplete="off")} +
+
+ +
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_perms.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_perms.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,5 @@ +## permissions overview +
+<%namespace name="p" file="/base/perms_summary.html"/> +${p.perms_summary(c.perm_user.permissions, actions=False)} +
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_profile.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_profile.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,73 @@ +${h.form(url('my_account'), method='post')} +
+ +
+
+
gravatar
+

+ %if c.visual.use_gravatar: + ${_('Change your avatar at')} gravatar.com +
${_('Using')} ${c.user.email} + %else: + ${_('Avatars are disabled')} +
${c.user.email or _('Missing email, please update your user email address.')} + [${_('current IP')}: ${c.perm_user.ip_addr or "?"}] + %endif +

+
+
+ + <% readonly = None %> + <% disabled = "" %> +
+ %if c.extern_type != 'rhodecode': + <% readonly = "readonly" %> + <% disabled = " disabled" %> + ${_('Your user is in an external Source of Record; some details cannot be managed here')}. + %endif +
+
+ +
+
+ ${h.text('username',class_='medium%s' % disabled, readonly=readonly)} + ${h.hidden('extern_name', c.extern_name)} + ${h.hidden('extern_type', c.extern_type)} +
+
+ +
+
+ +
+
+ ${h.text('firstname',class_="medium")} +
+
+ +
+
+ +
+
+ ${h.text('lastname',class_="medium")} +
+
+ +
+
+ +
+
+ ## we should be able to edit email ! + ${h.text('email',class_="medium")} +
+
+ +
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_pullrequests.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_pullrequests.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,66 @@ +%if c.show_closed: + ${h.checkbox('show_closed',checked="checked", label=_('Show closed pull requests'))} +%else: + ${h.checkbox('show_closed',label=_('Show closed pull requests'))} +%endif +
${_('Opened by me')}
+ + +
${_('I participate in')}
+ + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_repos.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_repos.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,107 @@ +
${_('Repositories you are owner of')}
+ + +
+
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/my_account/my_account_watched.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/my_account/my_account_watched.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,107 @@ +
${_('Repositories you are watching')}
+ + +
+
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/notifications/notifications.html --- a/rhodecode/templates/admin/notifications/notifications.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/notifications/notifications.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('My Notifications')} ${c.rhodecode_user.username} · ${c.rhodecode_name} + ${_('My Notifications')} ${c.rhodecode_user.username} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -26,13 +29,13 @@
%if c.notifications:
- ${_('Mark all read')} + ${_('Mark all read')}
%endif
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/notifications/notifications_data.html --- a/rhodecode/templates/admin/notifications/notifications_data.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/notifications/notifications_data.html Wed Jul 02 19:03:13 2014 -0400 @@ -14,17 +14,18 @@
- +
%if not notification.read:
- +
%endif
-
${h.literal(notification.notification.subject)}
+
%endfor
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/notifications/show_notification.html --- a/rhodecode/templates/admin/notifications/show_notification.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/notifications/show_notification.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Show notification')} ${c.rhodecode_user.username} · ${c.rhodecode_name} + ${_('Show notification')} ${c.rhodecode_user.username} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -36,7 +39,7 @@ ${c.notification.description}
- +
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/permissions/permissions.html --- a/rhodecode/templates/admin/permissions/permissions.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/permissions/permissions.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Permissions administration')} · ${c.rhodecode_name} + ${_('Permissions administration')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -15,177 +18,35 @@ ${self.menu('admin')} + <%def name="main()"> -
- +
${self.breadcrumbs()}
-

${_('Default permissions')}

- ${h.form(url('permission', id='default'),method='put')} -
- -
-
-
- -
-
-
- ${h.checkbox('anonymous',True)} -
-
-
-
-
- -
-
- ${h.select('default_repo_perm','',c.repo_perms_choices)} - - ${h.checkbox('overwrite_default_repo','true')} - -
-
-
-
- -
-
- ${h.select('default_group_perm','',c.group_perms_choices)} - ${h.checkbox('overwrite_default_group','true')} - -
-
-
-
- -
-
- ${h.select('default_user_group_perm','',c.user_group_perms_choices)} - ${h.checkbox('overwrite_default_user_group','true')} - + ##main +
+ +
-
-
-
-
- -
-
- ${h.select('default_repo_create','',c.repo_create_choices)} -
-
-
-
- -
-
- ${h.select('default_user_group_create','',c.user_group_create_choices)} -
-
-
-
- -
-
- ${h.select('default_fork','',c.fork_choices)} -
-
-
-
- -
-
- ${h.select('default_register','',c.register_choices)} -
-
-
-
- -
-
- ${h.select('default_extern_activate','',c.extern_activate_choices)} -
-
-
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
+
+ <%include file="/admin/permissions/permissions_${c.active}.html"/>
- ${h.end_form()}
-
- -
-
${_('Default User Permissions')}
-
- - ## permissions overview - <%namespace name="p" file="/base/perms_summary.html"/> - ${p.perms_summary(c.perm_user.permissions, show_all=True)} - -
-
- -
-
${_('Allowed IP addresses')}
-
- -
- - %if c.user_ip_map: - %for ip in c.user_ip_map: - - - - - - %endfor - %else: - - %endif -
${ip.ip_addr}
${h.ip_range(ip.ip_addr)}
- ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')} - ${h.hidden('del_ip',ip.ip_id)} - ${h.hidden('default_user', 'True')} - ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, - class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} - ${h.end_form()} -
${_('All IP addresses are allowed')}
-
- - ${h.form(url('user_ips', id=c.user.user_id),method='put')} -
- -
-
-
- -
-
- ${h.hidden('default_user', 'True')} - ${h.text('new_ip', class_='medium')} -
-
-
- ${h.submit('save',_('Add'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/permissions/permissions_globals.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/permissions/permissions_globals.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,113 @@ +${h.form(url('admin_permissions'), method='post')} +
+ +
+
+
+ +
+
+
+ ${h.checkbox('anonymous',True)} +
+ ${h.literal(_('Allow access to RhodeCode without need to log in. Anonymous users use %s user permissions' % (h.link_to('*default*',h.url('admin_permissions_perms')))))} +
+
+
+
+ +
+
+ ${h.select('default_repo_perm','',c.repo_perms_choices)} + + ${h.checkbox('overwrite_default_repo','true')} + +
+
+
+
+ +
+
+ ${h.select('default_group_perm','',c.group_perms_choices)} + ${h.checkbox('overwrite_default_group','true')} + + +
+
+
+
+ +
+
+ ${h.select('default_user_group_perm','',c.user_group_perms_choices)} + ${h.checkbox('overwrite_default_user_group','true')} + + +
+
+
+
+ +
+
+ ${h.select('default_repo_create','',c.repo_create_choices)} +
+
+
+
+ +
+
+ ${h.select('create_on_write','',c.repo_create_on_write_choices)} + ${_('Write permission to repository groups allows creating repositories inside that group')} +
+
+
+
+ +
+
+ ${h.select('default_user_group_create','',c.user_group_create_choices)} +
+
+
+
+ +
+
+ ${h.select('default_fork','',c.fork_choices)} +
+
+
+
+ +
+
+ ${h.select('default_register','',c.register_choices)} +
+
+
+
+ +
+
+ ${h.select('default_extern_activate','',c.extern_activate_choices)} +
+
+
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/permissions/permissions_ips.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/permissions/permissions_ips.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,45 @@ +

${_('Default ip whitelist for all users')}

+ +
+ + %if c.user_ip_map: + %for ip in c.user_ip_map: + + + + + + %endfor + %else: + + %endif +
${ip.ip_addr}
${h.ip_range(ip.ip_addr)}
+ ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')} + ${h.hidden('del_ip_id',ip.ip_id)} + ${h.hidden('default_user', 'True')} + ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, + class_="action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} + ${h.end_form()} +
${_('All IP addresses are allowed')}
+
+ +${h.form(url('edit_user_ips', id=c.user.user_id),method='put')} +
+ +
+
+
+ +
+
+ ${h.hidden('default_user', 'True')} + ${h.text('new_ip', class_='medium')} +
+
+
+ ${h.submit('save',_('Add'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/permissions/permissions_perms.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/permissions/permissions_perms.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,5 @@ +

${_('Default user permissions overview')}

+ +## permissions overview +<%namespace name="p" file="/base/perms_summary.html"/> +${p.perms_summary(c.perm_user.permissions, show_all=True)} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repo_groups/repo_group_add.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repo_groups/repo_group_add.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,98 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Add repository group')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} + » + ${h.link_to(_('Repository groups'),h.url('repos_groups'))} + » + ${_('Add Repository Group')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} +
+ + ${h.form(url('repos_groups'))} +
+ +
+
+
+ +
+
+ ${h.text('group_name',class_='small')} +
+
+ +
+
+ +
+
+ ${h.textarea('group_description',cols=23,rows=5,class_="medium")} +
+
+ +
+
+ +
+
+ ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")} +
+
+ +
+
+ +
+
+ ${h.checkbox('group_copy_permissions',value="True")} + ${_('Copy permission set from parent repository group.')} +
+
+ +
+ ${h.submit('save',_('Save'),class_="btn")} +
+
+
+ ${h.end_form()} +
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repo_groups/repo_group_edit.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,63 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('%s Repository group settings') % c.repo_group.name} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} + » + ${h.link_to(_('Repository Groups'),h.url('repos_groups'))} + %if c.repo_group.parent_group: + » ${h.link_to(c.repo_group.parent_group.name,h.url('repos_group_home',group_name=c.repo_group.parent_group.group_name))} + %endif + » ${c.repo_group.name} + + +<%def name="breadcrumbs_side_links()"> + + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+
+ ${self.breadcrumbs()} + ${self.breadcrumbs_side_links()} +
+ + ##main +
+ +
+ +
+ <%include file="/admin/repo_groups/repo_group_edit_${c.active}.html"/> +
+
+ diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,27 @@ +
${_('Repository Group: %s') % c.repo_group.group_name}
+ +
+<% + elems = [ + (_('Top level repositories'), c.repo_group.repositories.count(), ''), + (_('Total repositories'), c.repo_group.repositories_recursive_count, ''), + (_('Children groups'), c.repo_group.children.count(), ''), + (_('Created on'), h.fmt_date(c.repo_group.created_on), ''), + (_('Owner'), h.person(c.repo_group.user.username), ''), + ] +%> +%for dt, dd, tt in elems: +
${dt}:
+
${dd}
+%endfor +
+ +${h.form(h.url('repos_group', group_name=c.repo_group.group_name),method='delete')} + +${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,139 @@ +${h.form(url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put')} +
+
+
+ + + + + + + + + + ## USERS + %for r2p in c.repo_group.repo_group_to_perm: + ##forbid revoking permission from yourself, except if you're an super admin + + %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin: + + + + + + + %else: + + + + + + + %endif + + %endfor + + ## USER GROUPS + %for g2p in c.repo_group.users_group_to_perm: + + + + + + + + + %endfor + + <% + _tmpl = h.literal("""' \ + \ + \ + \ + \ + \ + '""") + %> + ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' + + + + + + + +
${_('none')}${_('read')}${_('write')}${_('admin')}${_('user/user group')}
${h.radio('u_perm_%s' % r2p.user.username,'group.none')}${h.radio('u_perm_%s' % r2p.user.username,'group.read')}${h.radio('u_perm_%s' % r2p.user.username,'group.write')}${h.radio('u_perm_%s' % r2p.user.username,'group.admin')} + + %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default': + ${r2p.user.username} + %else: + ${r2p.user.username if r2p.user.username != 'default' else _('default')} + %endif + + %if r2p.user.username !='default': + + ${_('revoke')} + + %endif + ${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")} + + ${r2p.user.username if r2p.user.username != 'default' else _('default')} + ${_('delegated admin')}
${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')} + + %if h.HasPermissionAny('hg.admin')(): + + ${g2p.users_group.users_group_name} + + %else: + ${g2p.users_group.users_group_name} + %endif + + + ${_('revoke')} + +
\ +
\ + \ + \ +
\ +
\ +
+ + ${_('Add new')} + +
+ ${_('apply to children')}: + ${h.radio('recursive', 'none', label=_('None'), checked="checked")} + ${h.radio('recursive', 'groups', label=_('Repository Groups'))} + ${h.radio('recursive', 'repos', label=_('Repositories'))} + ${h.radio('recursive', 'all', label=_('Both'))} + ${_('Set or revoke permission to all children of that group, including non-private repositories and other groups if selected.')} +
+
+
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,55 @@ +## -*- coding: utf-8 -*- +${h.form(url('repos_group',group_name=c.repo_group.group_name),method='put')} +
+ +
+
+
+ +
+
+ ${h.text('group_name',class_='medium')} +
+
+ +
+
+ +
+
+ ${h.textarea('group_description',cols=23,rows=5,class_="medium")} +
+
+ +
+
+ +
+
+ ${h.select('group_parent_id','',c.repo_groups,class_="medium")} +
+
+
+
+ +
+
+ ${h.checkbox('enable_locking',value="True")} + ${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')} +
+
+
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repo_groups/repo_group_show.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repo_groups/repo_group_show.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,26 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> +<%def name="title()"> + ${_('%s Repository group dashboard') % c.group.group_name} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs()"> + + ${h.link_to(_(u'Home'),h.url('/'))} + %if c.group.parent_group: + » ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))} + %endif + » "${c.group.name}" ${_('with')} + + + +<%def name="page_nav()"> + ${self.menu('repositories')} + + +<%def name="main()"> + <%include file="/index_base.html" args="parent=self,group_name=c.group.group_name"/> + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repo_groups/repo_groups.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repo_groups/repo_groups.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,60 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Repository groups administration')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + + ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('repository groups')} + + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} + +
+ +
+
+
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_add.html --- a/rhodecode/templates/admin/repos/repo_add.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/repos/repo_add.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Add repository')} · ${c.rhodecode_name} + ${_('Add repository')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -16,7 +19,7 @@ ${_('Repositories')} %endif » - ${_('Add new')} + ${_('Add Repository')} <%def name="page_nav()"> diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_add_base.html --- a/rhodecode/templates/admin/repos/repo_add_base.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/repos/repo_add_base.html Wed Jul 02 19:03:13 2014 -0400 @@ -10,12 +10,15 @@
${h.text('repo_name',class_="small")} + %if not c.rhodecode_user.is_admin: ${h.hidden('user_created',True)} %endif
-
+ -
-
+
+
+
+ +
+
+ ${h.textarea('repo_description')} + ${_('Keep it short and to the point. Use a README file for longer descriptions.')} +
+
+
@@ -32,7 +44,16 @@ ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")} ${_('Optionaly select a group to put this repository into.')}
-
+ +
+
+ +
+
+ ${h.checkbox('repo_copy_permissions',value="True")} + ${_('Copy permission set from parent repository group.')} +
+
@@ -41,26 +62,17 @@ ${h.select('repo_type','hg',c.backends,class_="small")} ${_('Type of repository to create.')}
-
-
+
+
${h.select('repo_landing_rev','',c.landing_revs,class_="medium")} - ${_('Default revision for files page, downloads, whoosh and readme')} + ${_('Default revision for files page, downloads, full text search index and readme generation')}
-
- -
-
- ${h.textarea('repo_description')} - ${_('Keep it short and to the point. Use a README file for longer descriptions.')} -
-
-
@@ -68,10 +80,49 @@ ${h.checkbox('repo_private',value="True")} ${_('Private repositories are only visible to people explicitly added as collaborators.')}
- +
- ${h.submit('add',_('Add'),class_="ui-btn large")} + ${h.submit('add',_('Add'),class_="btn")}
+ ${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_creating.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_creating.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,69 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +## don't trigger flash messages on this page +<%def name="flash_msg()"> + + +<%def name="title()"> + ${_('%s Creating repository') % c.repo_name} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + ${_('Creating repository')} ${c.repo} + + +<%def name="page_nav()"> + ${self.menu('repositories')} + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} +
+ +
+ ${_('Repository "%(repo_name)s" is beeing created, you will be redirected when this process is finished.' % {'repo_name':c.repo_name})} +
+ +
+
+
+
+
+
+ +
+ + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit.html --- a/rhodecode/templates/admin/repos/repo_edit.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/repos/repo_edit.html Wed Jul 02 19:03:13 2014 -0400 @@ -5,7 +5,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Edit repository')} ${c.repo_info.repo_name} · ${c.rhodecode_name} + ${_('%s repository settings') % c.repo_info.repo_name} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -13,376 +16,56 @@ <%def name="page_nav()"> - ${self.menu('admin')} + ${self.menu('repositories')} <%def name="main()"> ${self.repo_context_bar('options')} -
- -
- ${self.breadcrumbs()} -
- ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')} -
- -
-
-
- -
-
- ${h.text('repo_name',class_="medium")} - ${_('Non-changeable id')}: ${c.repo_info.repo_id} -
-
-
-
- -
-
- ${h.text('clone_uri',class_="medium")} - ${_('Optional http[s] url from which repository should be cloned.')} -
-
-
-
- -
-
- ${h.select('repo_group','',c.repo_groups,class_="medium")} - ${_('Optional select a group to put this repository into.')} -
-
-
-
- -
-
- ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")} - ${_('Default revision for files page, downloads, whoosh and readme')} -
-
-
-
- -
-
- ${h.textarea('repo_description')} - ${_('Keep it short and to the point. Use a README file for longer descriptions.')} -
-
+
+ + + -
-
- -
-
- ${h.checkbox('repo_private',value="True")} - ${_('Private repositories are only visible to people explicitly added as collaborators.')} -
-
-
-
- -
-
- ${h.checkbox('repo_enable_statistics',value="True")} - ${_('Enable statistics window on summary page.')} -
-
-
-
- -
-
- ${h.checkbox('repo_enable_downloads',value="True")} - ${_('Enable download menu on summary page.')} -
-
-
-
- -
-
- ${h.checkbox('repo_enable_locking',value="True")} - ${_('Enable lock-by-pulling on repository.')} -
-
-
-
- -
-
-
- ${h.text('user',class_='yui-ac-input')} - ${_('Change owner of this repository.')} -
-
-
-
- %if c.visual.repository_fields: - ## EXTRA FIELDS - %for field in c.repo_fields: -
-
- -
-
- ${h.text(field.field_key_prefixed, field.field_value, class_='medium')} - %if field.field_desc: - ${field.field_desc} - %endif -
-
- %endfor - %endif -
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
- -
-
-
${_('Permissions')}
-
- ${h.form(url('set_repo_perm_member', repo_name=c.repo_info.repo_name),method='post')} -
-
-
-
- -
-
- ${h.hidden('repo_private')} - <%include file="repo_edit_perms.html"/> -
-
-
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
- - -
-
-
${_('Advanced settings')}
+ ##main + -

${_('Statistics')}

- ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')} -
-
- ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")} -
-
    -
  • ${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}
  • -
  • ${_('Stats gathered')}: ${c.stats_percentage}%
  • -
-
-
-
- ${h.end_form()} - - %if c.repo_info.clone_uri: -

${_('Remote')}

- ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')} -
-
- ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")} - -
-
- ${h.end_form()} - %endif - -

${_('Cache')}

- ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')} -
-
- ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")} -
-
    -
  • ${_('Manually invalidate cache for this repository. On first access repository will be cached again')} -
  • -
-
-
- ${_('List of cached values')} - - - - - - - %for cache in c.repo_info.cache_keys: - - - - - - %endfor -
${_('Prefix')}${_('Key')}${_('Active')}
${cache.get_prefix() or '-'}${cache.cache_key}${h.boolicon(cache.cache_active)}
-
-
+
+ <%include file="/admin/repos/repo_edit_${c.active}.html"/>
- ${h.end_form()} - -

${_('Public journal')}

- ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')} -
- ${h.hidden('auth_token',str(h.get_token()))} -
- %if c.in_public_journal: - ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")} - %else: - ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")} - %endif -
-
-
    -
  • ${_('All actions made on this repository will be accessible to everyone in public journal')} -
  • -
-
-
- ${h.end_form()} - -

${_('Locking')}

- ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')} -
-
- %if c.repo_info.locked[0]: - ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")} - ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))} - %else: - ${h.submit('set_lock',_('Lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")} - ${_('Repository is not locked')} - %endif -
-
-
    -
  • ${_('Force locking on repository. Works only when anonymous access is disabled')} -
  • -
-
-
- ${h.end_form()} - -

${_('Set as fork of')}

- ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')} -
-
- ${h.select('id_fork_of','',c.repos_list,class_="medium")} - ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="ui-btn",)} -
-
-
    -
  • ${_('''Manually set this repository as a fork of another from the list''')}
  • -
-
-
- ${h.end_form()} - -

${_('Delete')}

- ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')} -
-
-
-##
-## -##
-
- ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")} - %if c.repo_info.forks.count(): - - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()} - - - %endif -
    -
  • ${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}
  • -
-
-
-
-
- ${h.end_form()}
-##TODO: this should be controlled by the VISUAL setting -%if c.visual.repository_fields: -
- -
-
${_('Extra fields')}
-
- -
- - %for field in c.repo_fields: - - - - - - %endfor -
${field.field_label} (${field.field_key})${field.field_type} - ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')} - ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id, - class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")} - ${h.end_form()} -
-
- - ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')} -
- -
-
-
- -
-
- ${h.text('new_field_key', class_='small')} -
-
-
-
- -
-
- ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))} -
-
- -
-
- -
-
- ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))} -
-
- -
- ${h.submit('save',_('Add'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
-%endif diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_advanced.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_advanced.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,97 @@ +

${_('Fork of')}

+${h.form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='put')} +
+
+ ${h.select('id_fork_of','',c.repos_list,class_="medium")} + ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small",)} +
+
+
    +
  • ${_('''Manually set this repository as a fork of another from the list''')}
  • +
+
+
+${h.end_form()} + + + +

${_('Public journal visibility')}

+${h.form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='put')} +
+ ${h.hidden('auth_token',str(h.get_token()))} +
+ %if c.in_public_journal: + + %else: + + %endif +
+
+
    +
  • ${_('All actions made on this repository will be accessible to everyone in public journal')}
  • +
+
+
+${h.end_form()} + +

${_('Change locking')}

+${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')} +
+
+ %if c.repo_info.locked[0]: + ${h.hidden('set_unlock', '1')} + + ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))} + %else: + ${h.hidden('set_lock', '1')} + + ${_('Repository is not locked')} + %endif +
+
+
    +
  • ${_('Force locking on repository. Works only when anonymous access is disabled. Trigering a pull locks repository by user who pulled, only the same user can unlock by doing a push')} +
  • +
+
+
+${h.end_form()} + +

${_('Delete')}

+${h.form(url('repo', repo_name=c.repo_name),method='delete')} + + %if c.repo_info.forks.count(): + ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()} + + + %endif +
+
    +
  • ${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}
  • +
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_caches.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_caches.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,30 @@ +${h.form(url('edit_repo_caches', repo_name=c.repo_name), method='put')} +
+
+ ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")} +
+
    +
  • ${_('Manually invalidate cache for this repository. On first access repository will be cached again')} +
  • +
+
+
+ ${_('List of cached values')} + + + + + + + %for cache in c.repo_info.cache_keys: + + + + + + %endfor +
${_('Prefix')}${_('Key')}${_('Active')}
${cache.get_prefix() or '-'}${cache.cache_key}${h.boolicon(cache.cache_active)}
+
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_fields.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_fields.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,68 @@ +%if c.visual.repository_fields: + %if c.repo_fields: +
+ + + + + + + %for field in c.repo_fields: + + + + + + + %endfor +
${_('Label')}${_('Key')}${_('Type')}${_('Action')}
${field.field_label}${field.field_key}${field.field_type} + ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')} + + ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id, + class_="action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")} + ${h.end_form()} +
+
+ %endif + ${h.form(url('create_repo_fields', repo_name=c.repo_name),method='put')} +
+ +
+
+
+ +
+
+ ${h.text('new_field_key', class_='small')} +
+
+
+
+ +
+
+ ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))} +
+
+ +
+
+ +
+
+ ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))} +
+
+ +
+ ${h.submit('save',_('Add'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} +%else: +
+ ${_('Extra fields are disabled')} +
+%endif diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_fork.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_fork.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,21 @@ +${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')} +
+
+ ${h.select('id_fork_of','',c.repos_list,class_="medium")} + ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small",)} +
+
+
    +
  • ${_('''Manually set this repository as a fork of another from the list''')}
  • +
+
+
+${h.end_form()} + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_permissions.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_permissions.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,125 @@ +${h.form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')} +
+
+
+ ${h.hidden('repo_private')} + + + + + + + + + + ## USERS + %for r2p in c.repo_info.repo_to_perm: + %if r2p.user.username =='default' and c.repo_info.private: + + + + + %else: + + + + + + + + + %endif + %endfor + + ## USER GROUPS + %for g2p in c.repo_info.users_group_to_perm: + + + + + + + + + %endfor + + <% + _tmpl = h.literal("""' \ + \ + \ + \ + \ + \ + '""") + %> + ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' + + + + +
${_('none')}${_('read')}${_('write')}${_('admin')}${_('user/user group')}
+ + ${_('private repository')} + + ${_('default')}
${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')} + + %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default': + ${r2p.user.username} + %else: + ${r2p.user.username if r2p.user.username != 'default' else _('default')} + %endif + + %if r2p.user.username !='default': + + ${_('revoke')} + + %endif +
${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')} + + %if h.HasPermissionAny('hg.admin')(): + ${g2p.users_group.users_group_name} + %else: + ${g2p.users_group.users_group_name} + %endif + + + ${_('revoke')} + +
\ +
\ + \ + \ +
\ +
\ +
+ + ${_('Add new')} + +
+
+
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_perms.html --- a/rhodecode/templates/admin/repos/repo_edit_perms.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ - - - - - - - - - - ## USERS - %for r2p in c.repo_info.repo_to_perm: - %if r2p.user.username =='default' and c.repo_info.private: - - - - - %else: - - - - - - - - - %endif - %endfor - - ## USER GROUPS - %for g2p in c.repo_info.users_group_to_perm: - - - - - - - - - %endfor - <% - _tmpl = h.literal("""' \ - \ - \ - \ - \ - \ - '""") - %> - ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' - - - - -
${_('none')}${_('read')}${_('write')}${_('admin')}${_('member')}
- - ${_('private repository')} - - ${_('default')}
${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')} - ${r2p.user.username if r2p.user.username != 'default' else _('default')} - - %if r2p.user.username !='default': - - ${_('revoke')} - - %endif -
${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')} - - %if h.HasPermissionAny('hg.admin')(): - ${g2p.users_group.users_group_name} - %else: - ${g2p.users_group.users_group_name} - %endif - - - ${_('revoke')} - -
\ -
\ - \ - \ -
\ -
\ -
- - ${_('Add another member')} - -
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_remote.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_remote.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,16 @@ +%if c.repo_info.clone_uri: +
+ ${_('Remote url')}: ${c.repo_info.clone_uri_hidden} +
+${h.form(url('edit_repo_remote', repo_name=c.repo_name), method='put')} +
+
+ ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")} +
+
+${h.end_form()} +%else: +
+ ${_('This repository does not have any remote url set')} +
+%endif diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_settings.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_settings.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,164 @@ +${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')} +
+ +
+
+
+ +
+
+ ${h.text('repo_name',class_="medium")} + ${_('Non-changeable id')}: `_${c.repo_info.repo_id}` ${_('what is that ?')} + +
+
+
+
+ +
+
+ %if c.repo_info.clone_uri: +
+ ${c.repo_info.clone_uri_hidden} + ${_('edit')} +
+ + %else: + ## not set yet, display form to set it + ${h.text('clone_uri',class_="medium")} + ${h.hidden('clone_uri_change', 'NEW')} + %endif + ${_('http[s] url used for doing remote pulls.')} +
+
+
+
+ +
+
+ ${h.select('repo_group','',c.repo_groups,class_="medium")} + ${_('Optional select a group to put this repository into.')} +
+
+
+
+ +
+
+ ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")} + ${_('Default revision for files page, downloads, whoosh and readme')} +
+
+
+
+ +
+
+
+ ${h.text('user',class_='yui-ac-input')} + ${_('Change owner of this repository.')} +
+
+
+
+
+
+ +
+
+ ${h.textarea('repo_description', style="height:165px")} + ${_('Keep it short and to the point. Use a README file for longer descriptions.')} +
+
+ +
+
+ +
+
+ ${h.checkbox('repo_private',value="True")} + ${_('Private repositories are only visible to people explicitly added as collaborators.')} +
+
+
+
+ +
+
+ ${h.checkbox('repo_enable_statistics',value="True")} + ${_('Enable statistics window on summary page.')} +
+
+
+
+ +
+
+ ${h.checkbox('repo_enable_downloads',value="True")} + ${_('Enable download menu on summary page.')} +
+
+
+
+ +
+
+ ${h.checkbox('repo_enable_locking',value="True")} + ${_('Enable lock-by-pulling on repository.')} +
+
+ + %if c.visual.repository_fields: + ## EXTRA FIELDS + %for field in c.repo_fields: +
+
+ +
+
+ ${h.text(field.field_key_prefixed, field.field_value, class_='medium')} + %if field.field_desc: + ${field.field_desc} + %endif +
+
+ %endfor + %endif +
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repo_edit_statistics.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/repos/repo_edit_statistics.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,13 @@ +${h.form(url('edit_repo_statistics', repo_name=c.repo_info.repo_name), method='put')} +
+
+
+
    +
  • ${_('Processed commits')}: ${c.stats_revision}/${c.repo_last_rev}
  • +
  • ${_('Processed progress')}: ${c.stats_percentage}%
  • +
+
+ ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")} +
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos/repos.html --- a/rhodecode/templates/admin/repos/repos.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/repos/repos.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Repositories administration')} · ${c.rhodecode_name} + ${_('Repositories administration')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -19,112 +22,41 @@
-
+
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html --- a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ - - - - - - - - - - ## USERS - %for r2p in c.repos_group.repo_group_to_perm: - ##forbid revoking permission from yourself - - %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin: - - - - - - - %else: - - - - - - - %endif - - %endfor - - ## USER GROUPS - %for g2p in c.repos_group.users_group_to_perm: - - - - - - - - - %endfor - - <% - _tmpl = h.literal("""' \ - \ - \ - \ - \ - \ - '""") - %> - ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' - - - - - - - -
${_('none')}${_('read')}${_('write')}${_('admin')}${_('member')}
${h.radio('u_perm_%s' % r2p.user.username,'group.none')}${h.radio('u_perm_%s' % r2p.user.username,'group.read')}${h.radio('u_perm_%s' % r2p.user.username,'group.write')}${h.radio('u_perm_%s' % r2p.user.username,'group.admin')} - ${r2p.user.username if r2p.user.username != 'default' else _('default')} - - %if r2p.user.username !='default': - - ${_('revoke')} - - %endif - ${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")} - ${r2p.user.username if r2p.user.username != 'default' else _('default')} - -
${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')} - ${g2p.users_group.users_group_name} - - - ${_('revoke')} - -
\ -
\ - \ - \ -
\ -
\ -
- - ${_('Add another member')} - -
- ${h.checkbox('recursive',value="True", label=_('apply to children'))} - ${_('Set or revoke permission to all children of that group, including non-private repositories and other groups')} -
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos_groups/repos_groups.html --- a/rhodecode/templates/admin/repos_groups/repos_groups.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> -<%def name="title()"> - ${_('%s Group Dashboard') % c.group.group_name} · ${c.rhodecode_name} - - -<%def name="breadcrumbs()"> - - ${h.link_to(_(u'Home'),h.url('/'))} - %if c.group.parent_group: - » ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))} - %endif - » "${c.group.name}" ${_('with')} - - - -<%def name="page_nav()"> - ${self.menu('repositories')} - - -<%def name="main()"> - <%include file="/index_base.html" args="parent=self,group_name=c.group.group_name"/> - diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos_groups/repos_groups_add.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_add.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('Add repository group')} · ${c.rhodecode_name} - - -<%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} - » - ${h.link_to(_('Repository groups'),h.url('repos_groups'))} - » - ${_('Add new repository group')} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} -
- - ${h.form(url('repos_groups'))} -
- -
-
-
- -
-
- ${h.text('group_name',class_='medium')} -
-
- -
-
- -
-
- ${h.textarea('group_description',cols=23,rows=5,class_="medium")} -
-
- -
-
- -
-
- ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")} -
-
- -
- ${h.submit('save',_('save'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos_groups/repos_groups_edit.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('Edit repository group')} ${c.repos_group.name} · ${c.rhodecode_name} - - -<%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} - » - ${h.link_to(_('Repository groups'),h.url('repos_groups'))} - » - ${_('Edit repository group %s') % c.repos_group.name} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} - -
- - ${h.form(url('repos_group',group_name=c.repos_group.group_name),method='put')} -
- -
-
-
- -
-
- ${h.text('group_name',class_='medium')} -
-
- -
-
- -
-
- ${h.textarea('group_description',cols=23,rows=5,class_="medium")} -
-
- -
-
- -
-
- ${h.select('group_parent_id','',c.repo_groups,class_="medium")} -
-
-
-
- -
-
- ${h.checkbox('enable_locking',value="True")} - ${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')} -
-
-
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
-
-
-
${_('Permissions')}
-
- ${h.form(url('set_repo_group_perm_member', group_name=c.repos_group.group_name),method='post')} -
-
-
-
- -
-
- ${h.hidden('repo_private')} - <%include file="repos_group_edit_perms.html"/> -
-
-
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/repos_groups/repos_groups_show.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('Repository groups administration')} · ${c.rhodecode_name} - - - -<%def name="breadcrumbs_links()"> - %if h.HasPermissionAny('hg.admin')(): - ${h.link_to(_('Admin'),h.url('admin_home'))} - %else: - ${_('Admin')} - %endif - » - ${_('Repository groups')} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} - -
- -
- % if c.groups: - - - - - - - - - - - - ## REPO GROUPS - - % for gr in c.groups: - <% gr_cn = gr.repositories.count() %> - - - - - - - - % endfor - -
${_('Group name')}${_('Description')}${_('Number of toplevel repositories')}${_('Action')}
-
- ${_('Repository group')} - ${h.link_to(h.literal(' » '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))), url('repos_group_home',group_name=gr.group_name))} -
-
${gr.group_description}${gr_cn} - - ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")} - - - ${h.form(url('repos_group', group_name=gr.group_name),method='delete')} - ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_cn) % (gr.name,gr_cn)+"');")} - ${h.end_form()} -
- % else: - ${_('There are no repository groups yet')} - % endif - -
-
- - diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/hooks.html --- a/rhodecode/templates/admin/settings/hooks.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('Settings administration')} · ${c.rhodecode_name} - - -<%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} » ${_('Settings')} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} -
- - -

${_('Built in hooks - read only')}

-
-
- % for hook in c.hooks: -
-
- -
-
- ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly")} -
-
- % endfor -
-
- % if c.visual.allow_custom_hooks_settings: -

${_('Custom hooks')}

- ${h.form(url('admin_setting', setting_id='hooks'),method='put')} -
-
- - % for hook in c.custom_hooks: -
-
- -
-
- ${h.hidden('hook_ui_key',hook.ui_key)} - ${h.hidden('hook_ui_value',hook.ui_value)} - ${h.text('hook_ui_value_new',hook.ui_value,size=60)} - - ${_('remove')} - -
-
- % endfor - -
-
-
- ${h.text('new_hook_ui_key',size=30)} -
-
-
- ${h.text('new_hook_ui_value',size=60)} -
-
-
- ${h.submit('save',_('Save'),class_="ui-btn large")} -
-
-
- ${h.end_form()} - % endif -
- - - diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings.html --- a/rhodecode/templates/admin/settings/settings.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/settings/settings.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Settings administration')} · ${c.rhodecode_name} + ${_('Settings administration')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -17,351 +20,39 @@ <%def name="main()">
-
${self.breadcrumbs()}
- - -

${_('Remap and rescan repositories')}

- ${h.form(url('admin_setting', setting_id='mapping'),method='put')} -
- - -
-
-
- -
-
-
- ${h.checkbox('destroy',True)} - -
-
- ${h.checkbox('invalidate',True)} - -
- ${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')} -
-
- -
- ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")} -
-
-
- ${h.end_form()} - -

${_('Whoosh indexing')}

- ${h.form(url('admin_setting', setting_id='whoosh'),method='put')} -
- - -
-
-
- -
-
-
- ${h.checkbox('full_index',True)} - -
-
-
- -
- ${h.submit('reindex',_('Reindex'),class_="ui-btn large")} -
-
-
- ${h.end_form()} - -

${_('Global application settings')}

- ${h.form(url('admin_setting', setting_id='global'),method='put')} -
- - -
- -
-
- -
-
- ${h.text('rhodecode_title',size=30)} -
-
- -
-
- -
-
- ${h.text('rhodecode_realm',size=30)} -
-
- -
-
- -
-
- ${h.text('rhodecode_ga_code',size=30)} -
-
- -
- ${h.submit('save',_('Save settings'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} - -

${_('Visualisation settings')}

- ${h.form(url('admin_setting', setting_id='visual'),method='put')} -
- - -
-
-
- -
-
-
- ${h.checkbox('rhodecode_repository_fields','True')} - -
- ${_('Allows storing additional customized fields per repository.')} -
- ${h.checkbox('rhodecode_show_version','True')} - -
- ${_('Shows or hides displayed version of RhodeCode in the footer')} -
-
-
-
- -
-
- ${h.text('rhodecode_dashboard_items',size=5)} - ${_('Number of items displayed in lightweight dashboard before pagination is shown.')} -
-
-
-
- -
-
-
- ${h.checkbox('rhodecode_show_public_icon','True')} - -
-
- ${h.checkbox('rhodecode_show_private_icon','True')} - -
- ${_('Show public/private icons next to repositories names')} -
-
-
-
- -
-
-
- ${h.checkbox('rhodecode_stylify_metatags','True')} - -
-
-
    -
  • [featured] featured
  • -
  • [stale] stale
  • -
  • [dead] dead
  • -
  • [lang => lang] lang
  • -
  • [license => License] License
  • -
  • [requires => Repo] requires => Repo
  • -
  • [recommends => Repo] recommends => Repo
  • -
  • [see => URI] see => URI
  • -
-
-
-
- -
- ${h.submit('save',_('Save settings'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
- -
-
- ${h.end_form()} - - -

${_('VCS settings')}

- ${h.form(url('admin_setting', setting_id='vcs'),method='put')} -
- - -
- -
-
- -
-
-
- ${h.checkbox('web_push_ssl', 'True')} - -
- ${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')} -
-
+ ##main +
+
-
- ${h.end_form()} - - - -

${_('Test Email')}

- ${h.form(url('admin_setting', setting_id='email'),method='put')} -
- - -
-
-
- -
-
- ${h.text('test_email',size=30)} -
-
- -
- ${h.submit('send',_('Send'),class_="ui-btn large")} -
-
-
- ${h.end_form()} - -

${_('System Info and Packages')}

-
-
-
↓ ${_('Show')} ↓
-
- +
- +
+ <%include file="/admin/settings/settings_${c.active}.html"/> +
+
-
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_email.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_email.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,43 @@ +
+<% + elems = [ + (_('Email prefix'), c.rhodecode_ini.get('email_prefix'), ''), + (_('RhodeCode email from'), c.rhodecode_ini.get('app_email_from'), ''), + (_('Error email from'), c.rhodecode_ini.get('error_email_from'), ''), + (_('Error email recipients'), c.rhodecode_ini.get('email_to'), ''), + + (_('SMTP server'), c.rhodecode_ini.get('smtp_server'), ''), + (_('SMTP username'), c.rhodecode_ini.get('smtp_username'), ''), + (_('SMTP password'), '%s chars' % len(c.rhodecode_ini.get('smtp_password', '')), ''), + (_('SMTP port'), c.rhodecode_ini.get('smtp_port'), ''), + + (_('SMTP use TLS'), c.rhodecode_ini.get('smtp_use_tls'), ''), + (_('SMTP use SSL'), c.rhodecode_ini.get('smtp_use_ssl'), ''), + (_('SMTP auth'), c.rhodecode_ini.get('smtp_auth'), ''), + ] +%> +%for dt, dd, tt in elems: +
${dt}:
+
${dd}
+%endfor +
+ +${h.form(url('admin_settings_email'), method='post')} +
+ +
+
+
+ +
+
+ ${h.text('test_email',size=30)} +
+
+ +
+ ${h.submit('send',_('Send'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_global.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_global.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,60 @@ +${h.form(url('admin_settings_global'), method='post')} +
+ +
+ +
+
+ +
+
+ ${h.text('rhodecode_title',size=30)} + ${_('Set a custom title for your RhodeCode Service.')} +
+
+ +
+
+ +
+
+ ${h.text('rhodecode_realm',size=30)} +
+
+ +
+
+ +
+
+ ${h.text('rhodecode_ga_code',size=30)} +
+
+ +
+
+ +
+
+ ${h.text('rhodecode_captcha_public_key',size=60)} + ${_('Public key for reCaptcha system.')} +
+
+ +
+
+ +
+
+ ${h.text('rhodecode_captcha_private_key',size=60)} + ${_('Private key for reCaptcha system. Setting this value will enable captcha on registration')} +
+
+ +
+ ${h.submit('save',_('Save settings'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_hooks.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_hooks.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,75 @@ +

${_('Built in Mercurial hooks - read only')}

+
+
+ % for hook in c.hooks: +
+
+ +
+
+ ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly")} +
+
+ % endfor +
+ ${_('Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications.')} +
+ +% if c.visual.allow_custom_hooks_settings: +

${_('Custom hooks')}

+${h.form(url('admin_settings_hooks'), method='post')} +
+
+ + % for hook in c.custom_hooks: +
+
+ +
+
+ ${h.hidden('hook_ui_key',hook.ui_key)} + ${h.hidden('hook_ui_value',hook.ui_value)} + ${h.text('hook_ui_value_new',hook.ui_value,size=60)} + + + ${_('delete')} + +
+
+ % endfor + +
+
+
+ ${h.text('new_hook_ui_key',size=30)} +
+
+
+ ${h.text('new_hook_ui_value',size=60)} +
+
+
+ ${h.submit('save',_('Save'),class_="btn")} +
+
+
+${h.end_form()} +% endif + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_license.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_license.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,59 @@ +%if not c.license_loaded: +${_("Currently you're using a free license, which is limited to 20 users.")} +

+%endif + +
+<% + elems = [ + (_('RhodeCode version'), c.rhodecode_version, ''), + (_('License token'), h.literal('
%s
' % c.rhodecode_ini.get('license_token', _('No license token'))), ''), + ] + if c.license_info: + elems.append((_('License issued to'), '%s %s (%s)' % (c.license_info.get('first_name'),c.license_info.get('last_name'),c.license_info.get('company')), '')) + elems.append((_('License issued on'), h.fmt_date(h.time_to_datetime(c.license_info.get('issue_date'))), '')) + elems.append((_('License users limit'), c.license_info.get('users') if c.license_info.get('users') != -1 else _('unlimited'), '')) + elems.append((_('License expires on'), h.fmt_date(h.time_to_datetime(c.license_info.get('valid_till'))), '')) +%> +%for dt, dd, tt in elems: +
${dt}:
+
${dd}
+%endfor +
+ + +%if c.license_token: +${h.form(url('admin_settings_license'), method='post')} +
+ +
+ +
+
+ +
+
+ ${h.textarea('rhodecode_license_key', style="height:165px")} + ${_('Paste your RhodeCode license key into that textarea.')} +
+
+ +
+ ${h.submit('save',_('Save settings'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} + + +%else: +
+${_('Please enter following lines (if yet not present) into [app:main] section of your .ini file. The token below is autogenerated.')} +
+
+<%text>## license token
+license_token = ${c.generated_license_token}
+
+
+%endif diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_mapping.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_mapping.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,36 @@ +${h.form(url('admin_settings_mapping'), method='post')} +
+
+
+
+ +
+
+
+ ${h.checkbox('destroy',True)} + +
+ ${_('In case a repository was deleted from filesystem and it still exists in the database check this option to scan obsolete data in database and remove it.')} + +
+ ${h.checkbox('invalidate',True)} + +
+ ${_('Each cache data for repositories will be cleaned with this option selected. Use this to reload data and clear cache keys.')} + +
+ ${h.checkbox('hooks',True)} + +
+ ${_('Verify if RhodeCodes GIT hooks are installed for each repository. Current hooks will be updated to latest version')} +
+ +
+
+ +
+ ${h.submit('rescan',_('Rescan Repositories'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_search.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_search.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,25 @@ +${h.form(url('admin_settings_search'), method='post')} +
+ +
+
+
+ +
+
+
+ ${h.checkbox('full_index',True)} + + +
+ ${_('This option completely reindex all the files within RhodeCode for proper fulltext search capabilities.')} + +
+
+ +
+ ${h.submit('reindex',_('Reindex'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_system.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_system.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,43 @@ +
+<% + elems = [ + (_('RhodeCode version'), h.literal('%s %s' % (c.rhodecode_version, _('check for updates'))), ''), + (_('Python version'), c.py_version, ''), + (_('Platform'), c.platform, ''), + (_('GIT version'), c.git_version, ''), + (_('GIT path'), c.rhodecode_ini.get('git_path'), ''), + (_('Upgrade info endpoint'), h.literal('%s
%s.' % (c.rhodecode_update_url, _('Note: please make sure this server can access this url'))), ''), + ] +%> + + +%for dt, dd, tt in elems: +
${dt}:
+
${dd}
+%endfor +
+ +

${_('Python packages')}

+ + + + + + %for key, value in c.modules: + + + + + %endfor + +
${key}${value}
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_system_update.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_system_update.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,27 @@ +## -*- coding: utf-8 -*- +## upgrade block rendered afte on-click check + +
+

+ +%if c.should_upgrade: + A new version is available: + %if c.latest_data.get('title'): + ${h.literal(c.latest_data['title'])} + %else: + ${c.latest_ver} + %endif +%else: + You already have the latest stable version. +%endif +

+ +% if c.should_upgrade and c.important_notices: +
Important notes for this release:
+
    + % for notice in c.important_notices: +
  • - ${notice}
  • + % endfor +
+% endif +
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_vcs.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_vcs.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,96 @@ +${h.form(url('admin_settings'), method='post')} +
+
+
+
+ +
+
+
+ ${h.checkbox('web_push_ssl', 'True')} + +
+ ${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')} +
+
+ +
+
+ +
+
+
+ ${h.checkbox('hooks_changegroup_repo_size','True')} + +
+
+ ${h.checkbox('hooks_changegroup_push_logger','True')} + +
+
+ ${h.checkbox('hooks_outgoing_pull_logger','True')} + +
+
+ ${h.checkbox('hooks_changegroup_update','True')} + +
+
+
+
+
+ +
+
+
+ ${h.checkbox('extensions_largefiles','True')} + +
+
+ ${h.checkbox('extensions_hgsubversion','True')} + +
+ ${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')} + ##
+ ## ${h.checkbox('extensions_hggit','True')} + ## + ##
+ ##${_('Requires hg-git library to be installed. Allows cloning remote git repositories and migrates them to Mercurial type.')} +
+
+ %if c.visual.allow_repo_location_change: +
+
+ +
+
+ ${h.text('paths_root_path',size=60,readonly="readonly", class_="disabled")} + +
+
+ ${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')} +
+
+ %else: + ## form still requires this but we cannot internally change it anyway + ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")} + %endif +
+ ${h.submit('save',_('Save settings'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/settings/settings_visual.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/settings/settings_visual.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,121 @@ +${h.form(url('admin_settings_visual'), method='post')} +
+ +
+ +
+
+ +
+
+
+ ${h.checkbox('rhodecode_repository_fields','True')} + +
+ ${_('Allows storing additional customized fields per repository.')} +
+ ${h.checkbox('rhodecode_show_version','True')} + +
+ ${_('Shows or hides a version number of RhodeCode displayed in the footer.')} + +
+ ${h.checkbox('rhodecode_use_gravatar','True')} + +
+
+
+
+ ${h.text('rhodecode_gravatar_url', size=80)} + ${_('''Gravatar url allows you to use other avatar server application. + Following variables of the URL will be replaced accordingly. + {scheme} 'http' or 'https' sent from running RhodeCode server, + {email} user email, + {md5email} md5 hash of the user email (like at gravatar.com), + {size} size of the image that is expected from the server application, + {netloc} network location/server host of running RhodeCode server''')} +
+
+
+
+ ${h.text('rhodecode_clone_uri_tmpl', size=80)} + ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars: + {scheme} 'http' or 'https' sent from running RhodeCode server, + {user} current user username, + {netloc} network location/server host of running RhodeCode server, + {repo} full repository name, + {repoid} ID of repository, can be used to contruct clone-by-id''')} +
+
+
+ +
+
+ +
+
+ ${h.text('rhodecode_dashboard_items',size=5)} + ${_('Number of items displayed in the main page dashboard before pagination is shown.')} +
+
+ +
+
+ +
+
+ ${h.text('rhodecode_admin_grid_items',size=5)} + ${_('Number of items displayed in the admin pages grids before pagination is shown.')} +
+
+ +
+
+ +
+
+
+ ${h.checkbox('rhodecode_show_public_icon','True')} + +
+
+ ${h.checkbox('rhodecode_show_private_icon','True')} + +
+ ${_('Show public/private icons next to repositories names.')} +
+
+ +
+
+ +
+
+
+ ${h.checkbox('rhodecode_stylify_metatags','True')} + +
+
+
    +
  • [featured] featured
  • +
  • [stale] stale
  • +
  • [dead] dead
  • +
  • [lang => lang] lang
  • +
  • [license => License] License
  • +
  • [requires => Repo] requires => Repo
  • +
  • [recommends => Repo] recommends => Repo
  • +
  • [see => URI] see => URI
  • +
+
+ ${_('Parses meta tags from repository description field and turns them into colored tags.')} +
+
+ +
+ ${h.submit('save',_('Save settings'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+ +
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_group_add.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_group_add.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,72 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Add user group')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} + » + ${h.link_to(_('User groups'),h.url('users_groups'))} + » + ${_('Add User Group')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} +
+ + ${h.form(url('users_groups'))} +
+ +
+
+
+ +
+
+ ${h.text('users_group_name',class_='small')} +
+
+
+
+ +
+
+ ${h.textarea('user_group_description')} + ${_('Short, optional description for this user group.')} +
+
+
+
+ +
+
+ ${h.checkbox('users_group_active',value=True, checked='checked')} +
+
+ +
+ ${h.submit('save',_('Save'),class_="btn")} +
+
+
+ ${h.end_form()} +
+ + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_group_edit.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_group_edit.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,54 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('%s user group settings') % c.user_group.users_group_name} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Admin'),h.url('admin_home'))} + » + ${h.link_to(_('User Groups'),h.url('users_groups'))} + » + ${c.user_group.users_group_name} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+
+ ${self.breadcrumbs()} +
+ + ##main +
+ +
+ +
+ <%include file="/admin/user_groups/user_group_edit_${c.active}.html"/> +
+
+ diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_group_edit_advanced.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_group_edit_advanced.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,23 @@ +
${_('User Group: %s') % c.user_group.users_group_name}
+ +
+<% + elems = [ + (_('Members'), len(c.group_members_obj), ''), + (_('Created on'), h.fmt_date(c.user_group.created_on), ''), + (_('Owner'), h.person(c.user_group.user.username), ''), + ] +%> +%for dt, dd, tt in elems: +
${dt}:
+
${dd}
+%endfor +
+ +${h.form(h.url('users_group', id=c.user_group.users_group_id),method='delete')} + +${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_group_edit_default_perms.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_group_edit_default_perms.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,6 @@ +<%namespace name="dpb" file="/base/default_perms_box.html"/> +${dpb.default_perms_box(url('edit_user_group_default_perms', id=c.user_group.users_group_id))} + +## permissions overview +<%namespace name="p" file="/base/perms_summary.html"/> +${p.perms_summary(c.permissions)} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_group_edit_members.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_group_edit_members.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,17 @@ +
+% if c.group_members_obj: +
    + %for user in c.group_members_obj: +
  • +
    +
    gravatar
    +
    ${h.link_to(user.username, h.url('edit_user',id=user.user_id))}
    +
    ${user.full_name}
    +
    +
  • + %endfor +
+ %else: + ${_('No members yet')} + %endif +
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_group_edit_perms.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_group_edit_perms.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,128 @@ +${h.form(url('edit_user_group_perms', id=c.user_group.users_group_id),method='put')} +
+
+
+ + + + + + + + + + ## USERS + %for r2p in c.user_group.user_user_group_to_perm: + ##forbid revoking permission from yourself, except if you're an super admin + + %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin: + + + + + + + %else: + + + + + + + %endif + + %endfor + + ## USER GROUPS + %for g2p in c.user_group.user_group_user_group_to_perm: + + + + + + + + + %endfor + + <% + _tmpl = h.literal("""' \ + \ + \ + \ + \ + \ + '""") + %> + ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' + + + + +
${_('none')}${_('read')}${_('write')}${_('admin')}${_('user/user group')}
${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')} + + %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default': + ${r2p.user.username} + %else: + ${r2p.user.username if r2p.user.username != 'default' else _('default')} + %endif + + %if r2p.user.username !='default': + + ${_('revoke')} + + %endif + ${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")} + + ${r2p.user.username if r2p.user.username != 'default' else _('default')} + ${_('delegated admin')}
${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.none')}${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.read')}${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.write')}${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.admin')} + + %if h.HasPermissionAny('hg.admin')(): + + ${g2p.user_group.users_group_name} + + %else: + ${g2p.user_group.users_group_name} + %endif + + + ${_('revoke')} + +
\ +
\ + \ + \ +
\ +
\ +
+ + ${_('Add new')} + +
+
+
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} + + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_group_edit_settings.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_group_edit_settings.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,74 @@ +${h.form(url('users_group', id=c.user_group.users_group_id),method='put', id='edit_users_group')} +
+ +
+
+
+ +
+
+ ${h.text('users_group_name',class_='large')} +
+
+
+
+ +
+
+ ${h.textarea('user_group_description')} + ${_('Short, optional description for this user group.')} +
+
+
+
+ +
+
+ ${h.checkbox('users_group_active',value=True)} +
+
+
+
+ +
+
+ + + + +
+
+
+
${_('Chosen group members')}
+ ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")} +
+ ${_('Remove all elements')} + +
+
+
+ +
+ +
+
+
${_('Available members')}
+ ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")} +
+ ${_('Add all elements')} +
+
+
+
+
+ +
+
+ ${h.submit('Save',_('Save'),class_="btn")} +
+
+
+${h.end_form()} + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/user_groups/user_groups.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/user_groups/user_groups.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,61 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('User groups administration')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif + + +<%def name="breadcrumbs_links()"> + + ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('user groups')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} + +
+ +
+
+
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_add.html --- a/rhodecode/templates/admin/users/user_add.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/users/user_add.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,14 +2,17 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Add user')} · ${c.rhodecode_name} + ${_('Add user')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> ${h.link_to(_('Admin'),h.url('admin_home'))} » ${h.link_to(_('Users'),h.url('users'))} » - ${_('Add new user')} + ${_('Add User')} <%def name="page_nav()"> @@ -78,6 +81,8 @@
${h.text('email',class_='small')} + ${h.hidden('extern_name', c.default_extern_type)} + ${h.hidden('extern_type', c.default_extern_type)}
@@ -91,10 +96,15 @@
- ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.submit('save',_('Save'),class_="btn")}
${h.end_form()} + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit.html --- a/rhodecode/templates/admin/users/user_edit.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/users/user_edit.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,7 +2,10 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Edit user')} ${c.user.username} · ${c.rhodecode_name} + ${_('%s user settings') % c.user.username} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> @@ -10,7 +13,7 @@ » ${h.link_to(_('Users'),h.url('users'))} » - ${_('Edit %s') % c.user.username} + ${c.user.username} <%def name="page_nav()"> @@ -18,235 +21,36 @@ <%def name="main()"> -
- +
${self.breadcrumbs()}
- - ${h.form(url('update_user', id=c.user.user_id),method='put')} -
-
-
-
gravatar
-

- %if c.use_gravatar: - ${_('Change your avatar at')} gravatar.com -
${_('Using')} ${c.user.email} - %else: -
${c.user.email} - %endif + + ##main +

+
-
-
- ${c.user.api_key} -
-
- ##show current ip just if we show ourself - %if c.rhodecode_user.username == c.user.username: -
-
- ${c.perm_user.ip_addr or "?"} -
-
- %endif -
-
-
- -
-
- %if c.ldap_dn: - ${h.text('username',class_='medium disabled', readonly="readonly")} - %else: - ${h.text('username',class_='medium')} - %endif: -
-
- -
-
- -
-
- ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")} -
-
- -
-
- -
-
- ${h.password('new_password',class_='medium',autocomplete="off")} -
-
- -
-
- -
-
- ${h.password('password_confirmation',class_="medium",autocomplete="off")} -
-
- -
-
- -
-
- ${h.text('firstname',class_='medium')} -
-
- -
-
- -
-
- ${h.text('lastname',class_='medium')} -
-
- -
-
- -
-
- ${h.text('email',class_='medium')} -
-
- -
-
- -
-
- ${h.checkbox('active',value=True)} -
-
- -
-
- -
-
- ${h.checkbox('admin',value=True)} -
-
-
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
-
- -
-
${_('Permissions')}
-
- <%namespace name="dpb" file="/base/default_perms_box.html"/> - ${dpb.default_perms_box(url('user_perm', id=c.user.user_id))} - - ## permissions overview - <%namespace name="p" file="/base/perms_summary.html"/> - ${p.perms_summary(c.perm_user.permissions, show_all=True)} - -
-
- - -
- - %for em in c.user_email_map: - - - - - - %endfor -
gravatar
- ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')} - ${h.hidden('del_email',em.email_id)} - ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id, - class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")} - ${h.end_form()} -
+
+ <%include file="/admin/users/user_edit_${c.active}.html"/>
- - ${h.form(url('user_emails', id=c.user.user_id),method='put')} -
- -
-
-
- -
-
- ${h.text('new_email', class_='medium')} -
-
-
- ${h.submit('save',_('Add'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()}
-
- -
-
${_('Allowed IP addresses')}
-
-
- - %if c.user_ip_map: - %for ip in c.user_ip_map: - - - - - - %endfor - %else: - - %endif -
${ip.ip_addr}
${h.ip_range(ip.ip_addr)}
- ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')} - ${h.hidden('del_ip',ip.ip_id)} - ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, - class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} - ${h.end_form()} -
${_('All IP addresses are allowed')}
-
- - ${h.form(url('user_ips', id=c.user.user_id),method='put')} -
- -
-
-
- -
-
- ${h.text('new_ip', class_='medium')} -
-
-
- ${h.submit('save',_('Add'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_advanced.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/users/user_edit_advanced.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,25 @@ +
${_('User: %s') % c.user.username}
+ +
+<% + elems = [ + (_('Repositories'), len(c.user.repositories), ', '.join((x.repo_name for x in c.user.repositories))), + (_('Source of Record'), c.user.extern_type, ''), + (_('Created on'), h.fmt_date(c.user.created_on), ''), + (_('Last Login'), c.user.last_login or '-', ''), + (_('Member of User groups'), len(c.user.group_member), ', '.join((x.users_group.users_group_name for x in c.user.group_member))) + ] +%> +%for dt, dd, tt in elems: +
${dt}:
+
${dd}
+%endfor +
+ +${h.form(h.url('delete_user', id=c.user.user_id),method='delete')} + +${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_api_keys.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/users/user_edit_api_keys.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,83 @@ +
+ + + + + + + + %if c.user_api_keys: + %for api_key in c.user_api_keys: + + + + + + + %endfor + %else: + + %endif +
${c.user.api_key}
+ ${_('Built-in')} + ${_('expires')}: ${_('never')} + ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')} + ${h.hidden('del_api_key',c.user.api_key)} + ${h.hidden('del_api_key_builtin',1)} + + ${h.end_form()} +
${api_key.api_key}
${api_key.description} + %if api_key.expires == -1: + ${_('expires')}: ${_('never')} + %else: + %if api_key.expired: + ${_('expired')}: ${h.age(h.time_to_datetime(api_key.expires))} + %else: + ${_('expires')}: ${h.age(h.time_to_datetime(api_key.expires))} + %endif + %endif + + ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')} + ${h.hidden('del_api_key',api_key.api_key)} + + ${h.end_form()} +
${_('No additional api keys specified')}
+
+ +
+ ${h.form(url('edit_user_api_keys', id=c.user.user_id), method='put')} +
+ +
+
+
+ +
+
+ ${h.text('description', class_='medium', placeholder=_('Description'))} + ${h.select('lifetime', '', c.lifetime_options)} +
+
+
+ ${h.submit('save',_('Add'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} +
+ + diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_emails.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/users/user_edit_emails.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,51 @@ +
+ + + + + + + %if c.user_email_map: + %for em in c.user_email_map: + + + + + + %endfor + %else: + + %endif +
gravatar
+ ${_('Primary')} +
gravatar
+ ${h.form(url('edit_user_emails', id=c.user.user_id),method='delete')} + ${h.hidden('del_email_id',em.email_id)} + + ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id, + class_="action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")} + ${h.end_form()} +
${_('No additional emails specified')}
+
+ +
+ ${h.form(url('edit_user_emails', id=c.user.user_id),method='put')} +
+ +
+
+
+ +
+
+ ${h.text('new_email', class_='medium')} +
+
+
+ ${h.submit('save',_('Add'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} +
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_ips.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/users/user_edit_ips.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,55 @@ +
+ + %if c.default_user_ip_map and c.inherit_default_ips: + %for ip in c.default_user_ip_map: + + + + + + %endfor + %endif + + %if c.user_ip_map: + %for ip in c.user_ip_map: + + + + + + %endfor + %endif + %if not c.default_user_ip_map and not c.user_ip_map: + + %endif +
${ip.ip_addr}
${h.ip_range(ip.ip_addr)}
${h.literal(_('Inherited from %s') % h.link_to('*default*',h.url('admin_permissions_ips')))}
${ip.ip_addr}
${h.ip_range(ip.ip_addr)}
+ ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')} + ${h.hidden('del_ip_id',ip.ip_id)} + + ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, + class_="action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} + ${h.end_form()} +
${_('All IP addresses are allowed')}
+
+ +
+ ${h.form(url('edit_user_ips', id=c.user.user_id),method='put')} +
+ +
+
+
+ +
+
+ ${h.text('new_ip', class_='medium')} +
+
+
+ ${h.submit('save',_('Add'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+ ${h.end_form()} +
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_my_account.html --- a/rhodecode/templates/admin/users/user_edit_my_account.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,232 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('My account')} ${c.rhodecode_user.username} · ${c.rhodecode_name} - - -<%def name="breadcrumbs_links()"> - ${_('My Account')} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> - -
- -
- ${self.breadcrumbs()} -
- - ${c.form|n} -
- -
- - - - ## permissions overview -
- <%namespace name="p" file="/base/perms_summary.html"/> - ${p.perms_summary(c.perm_user.permissions)} -
- - - -
- - - diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_my_account_form.html --- a/rhodecode/templates/admin/users/user_edit_my_account_form.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -
- ${h.form(url('admin_settings_my_account_update'),method='put')} -
- -
-
-
gravatar
-

- %if c.use_gravatar: - ${_('Change your avatar at')} gravatar.com -
${_('Using')} ${c.user.email} - %else: -
${c.user.email} - %endif -

-
-
-
-
-
- ${c.perm_user.ip_addr or "?"} -
-
-
- -
${c.user.api_key}
-
-
-
-
-
- -
-
- %if c.ldap_dn: - ${h.text('username',class_='medium disabled', readonly="readonly")} - %else: - ${h.text('username',class_='medium')} - %endif: -
-
- -
-
- -
-
- ${h.password('new_password',class_="medium",autocomplete="off")} -
-
- -
-
- -
-
- ${h.password('password_confirmation',class_="medium",autocomplete="off")} -
-
- -
-
- -
-
- ${h.text('firstname',class_="medium")} -
-
- -
-
- -
-
- ${h.text('lastname',class_="medium")} -
-
- -
-
- -
-
- ${h.text('email',class_="medium")} -
-
- -
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html --- a/rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -%if c.show_closed: - ${h.checkbox('show_closed',checked="checked", label=_('Show closed pull requests'))} -%else: - ${h.checkbox('show_closed',label=_('Show closed pull requests'))} -%endif -
${_('Opened by me')}
- - -
${_('I participate in')}
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_my_account_repos.html diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_perms.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/users/user_edit_perms.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,6 @@ +<%namespace name="dpb" file="/base/default_perms_box.html"/> +${dpb.default_perms_box(url('edit_user_perms', id=c.user.user_id))} + +## permissions overview +<%namespace name="p" file="/base/perms_summary.html"/> +${p.perms_summary(c.perm_user.permissions, show_all=True)} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/user_edit_profile.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/users/user_edit_profile.html Wed Jul 02 19:03:13 2014 -0400 @@ -0,0 +1,127 @@ +${h.form(url('update_user', id=c.user.user_id),method='put')} +
+
+
+
gravatar
+

+ %if c.visual.use_gravatar: + ${_('Change avatar at')} gravatar.com +
${_('Using')} ${c.user.email} + %else: + ${_('Avatars are disabled')} +
${c.user.email or _('Missing email, please update this user email address.')} + ##show current ip just if we show ourself + %if c.rhodecode_user.username == c.user.username: + [${_('current IP')}: ${c.perm_user.ip_addr or "?"}] + %endif + %endif +

+
+ <% readonly = None %> + <% disabled = "" %> +
+ %if c.extern_type != 'rhodecode': +
+ <% readonly = "readonly" %> + <% disabled = " disabled" %> + ${_('This user is in an external Source of Record (%s); some details cannot be managed here.' % c.extern_type)}. +
+ %endif + +
+
+ +
+
+ ${h.text('username',class_='medium%s' % disabled, readonly=readonly)} +
+
+ +
+
+ +
+
+ ${h.text('email',class_='medium')} +
+
+ +
+
+ +
+
+ ${h.text('extern_type',class_='medium disabled',readonly="readonly")} +
+
+ +
+
+ +
+
+ ${h.text('extern_name',class_='medium disabled',readonly="readonly")} +
+
+ +
+
+ +
+
+ ${h.password('new_password',class_='medium%s' % disabled,autocomplete="off",readonly=readonly)} +
+
+ +
+
+ +
+
+ ${h.password('password_confirmation',class_="medium%s" % disabled,autocomplete="off",readonly=readonly)} +
+
+ +
+
+ +
+
+ ${h.text('firstname',class_='medium')} +
+
+ +
+
+ +
+
+ ${h.text('lastname',class_='medium')} +
+
+ +
+
+ +
+
+ ${h.checkbox('active',value=True)} +
+
+ +
+
+ +
+
+ ${h.checkbox('admin',value=True)} +
+
+ +
+ ${h.submit('save',_('Save'),class_="btn")} + ${h.reset('reset',_('Reset'),class_="btn")} +
+
+
+${h.end_form()} diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users/users.html --- a/rhodecode/templates/admin/users/users.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/admin/users/users.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,11 +2,15 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Users administration')} · ${c.rhodecode_name} + ${_('Users administration')} + %if c.rhodecode_name: + · ${c.rhodecode_name} + %endif <%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('users')} + + ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('users')} <%def name="page_nav()"> @@ -20,125 +24,44 @@ ${self.breadcrumbs()}
-
+
diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users_groups/user_group_edit_perms.html --- a/rhodecode/templates/admin/users_groups/user_group_edit_perms.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ - - - - - - - - - - ## USERS - %for r2p in c.users_group.user_user_group_to_perm: - ##forbid revoking permission from yourself - - %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin: - - - - - - - %else: - - - - - - - %endif - - %endfor - - ## USER GROUPS - %for g2p in c.users_group.user_group_user_group_to_perm: - - - - - - - - - %endfor - - <% - _tmpl = h.literal("""' \ - \ - \ - \ - \ - \ - '""") - %> - ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' - - - - -
${_('none')}${_('read')}${_('write')}${_('admin')}${_('member')}
${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')} - ${r2p.user.username if r2p.user.username != 'default' else _('default')} - - %if r2p.user.username !='default': - - ${_('revoke')} - - %endif - ${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")} - ${r2p.user.username if r2p.user.username != 'default' else _('default')} - -
${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.none')}${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.read')}${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.write')}${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.admin')} - ${g2p.user_group.users_group_name} - - - ${_('revoke')} - -
\ -
\ - \ - \ -
\ -
\ -
- - ${_('Add another member')} - -
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users_groups/users_group_add.html --- a/rhodecode/templates/admin/users_groups/users_group_add.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('Add user group')} · ${c.rhodecode_name} - -<%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} - » - ${h.link_to(_('User groups'),h.url('users_groups'))} - » - ${_('Add new user group')} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} -
- - ${h.form(url('users_groups'))} -
- -
-
-
- -
-
- ${h.text('users_group_name',class_='small')} -
-
- -
-
- -
-
- ${h.checkbox('users_group_active',value=True, checked='checked')} -
-
- -
- ${h.submit('save',_('Save'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users_groups/users_group_edit.html --- a/rhodecode/templates/admin/users_groups/users_group_edit.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('Edit user group')} ${c.users_group.users_group_name} · ${c.rhodecode_name} - - -<%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} - » - ${h.link_to(_('UserGroups'),h.url('users_groups'))} - » - ${_('Edit %s') % c.users_group.users_group_name} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} -
- - ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')} -
- -
-
-
- -
-
- ${h.text('users_group_name',class_='small')} -
-
- -
-
- -
-
- ${h.checkbox('users_group_active',value=True)} -
-
-
-
- -
-
- - - - -
-
-
-
${_('Chosen group members')}
- ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")} -
- ${_('Remove all elements')} - remove -
-
-
- add -
- remove -
-
-
${_('Available members')}
- ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")} -
- add - ${_('Add all elements')} -
-
-
-
-
- -
-
- ${h.submit('Save',_('Save'),class_="ui-btn large")} -
-
-
-${h.end_form()} -
- % if c.group_members_obj: -
    - %for user in c.group_members_obj: -
  • -
    -
    gravatar
    -
    ${h.link_to(user.username, h.url('edit_user',id=user.user_id))}
    -
    ${user.full_name}
    -
    -
  • - %endfor -
- %else: - ${_('No members yet')} - %endif -
-
- -
- -
-
${_('Global Permissions')}
-
- <%namespace name="dpb" file="/base/default_perms_box.html"/> - ${dpb.default_perms_box(url('users_group_perm', id=c.users_group.users_group_id))} - - ## permissions overview - <%namespace name="p" file="/base/perms_summary.html"/> - ${p.perms_summary(c.permissions)} -
- -
-
-
${_('Permissions')}
-
- ${h.form(url('set_user_group_perm_member', id=c.users_group.users_group_id),method='post')} -
-
-
-
- -
-
- <%include file="user_group_edit_perms.html"/> -
-
-
- ${h.submit('save',_('Save'),class_="ui-btn large")} - ${h.reset('reset',_('Reset'),class_="ui-btn large")} -
-
-
- ${h.end_form()} -
- - - diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/admin/users_groups/users_groups.html --- a/rhodecode/templates/admin/users_groups/users_groups.html Wed Jul 02 19:03:10 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -## -*- coding: utf-8 -*- -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${_('User groups administration')} · ${c.rhodecode_name} - - -<%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} - » - ${_('User groups')} - - -<%def name="page_nav()"> - ${self.menu('admin')} - - -<%def name="main()"> -
- -
- ${self.breadcrumbs()} - -
- - -
- %if c.users_groups_list: - - - - - - - - %for cnt,u_group in enumerate(c.users_groups_list): - - - - - - - - %endfor -
${_('Group name')}${_('Members')}${_('Active')}${_('Action')}
${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}${len(u_group.members)}${h.boolicon(u_group.users_group_active)} - - ${h.submit('edit_%s' % u_group.users_group_name,_('edit'),class_="edit_icon action_button")} - - - ${h.form(url('users_group', id=u_group.users_group_id),method='delete')} - ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id, - class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user group: %s') % u_group.users_group_name+"');")} - ${h.end_form()} -
- %else: - ${_('There are no user groups yet')} - %endif -
-
- diff -r 8b7294a804a0 -r ffd45b185016 rhodecode/templates/base/base.html --- a/rhodecode/templates/base/base.html Wed Jul 02 19:03:10 2014 -0400 +++ b/rhodecode/templates/base/base.html Wed Jul 02 19:03:13 2014 -0400 @@ -2,11 +2,15 @@ <%inherit file="root.html"/> -