diff options
author | Marcin Kuzminski <marcin@python-works.com> | 2012-01-28 01:06:29 +0200 |
---|---|---|
committer | Marcin Kuzminski <marcin@python-works.com> | 2012-01-28 01:06:29 +0200 |
commit | d4e540f70af3cf79b8a13cbcdb6a74b6c6d75af9 (patch) | |
tree | d7d612464c0748a61654cfb0574de87c27808e27 /rhodecode | |
parent | c6b3ec29e04bc5494ffdcb58840fc532080d697f (diff) |
#227 Initial version of repository groups permissions system
- implemented none/read/write/admin permissions for groups
- wrote more tests for permissions, and new permissions groups
- a lot of code garden, splitted logic into proper models
- permissions on groups doesn't propagate yet to repositories
- deprecated some methods on api for managing permissions on
repositories for users, and users groups
--HG--
branch : beta
Diffstat (limited to 'rhodecode')
25 files changed, 1374 insertions, 373 deletions
diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py index 84b5e88d..3ef59fd7 100644 --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -51,8 +51,8 @@ def make_app(global_conf, full_stack=True, static_files=True, **app_conf): from rhodecode.lib.profiler import ProfilingMiddleware app = ProfilingMiddleware(app) - if asbool(full_stack): + # Handle Python exceptions app = ErrorHandler(app, global_conf, **config['pylons.errorware']) @@ -80,7 +80,6 @@ def make_app(global_conf, full_stack=True, static_files=True, **app_conf): app = Cascade([static_app, app]) app = make_gzip_middleware(app, global_conf, compress_level=1) - app.config = config return app diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py index 28d2faf1..f0ffaccc 100644 --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -113,8 +113,9 @@ def make_map(config): function=check_repo)) #ajax delete repo perm user m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}", - action="delete_perm_user", conditions=dict(method=["DELETE"], - function=check_repo)) + action="delete_perm_user", + conditions=dict(method=["DELETE"], function=check_repo)) + #ajax delete repo perm users_group m.connect('delete_repo_users_group', "/repos_delete_users_group/{repo_name:.*}", @@ -128,7 +129,7 @@ def make_map(config): 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:.*}", + 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:.*}", @@ -169,6 +170,17 @@ def make_map(config): m.connect("formatted_repos_group", "/repos_groups/{id}.{format}", action="show", conditions=dict(method=["GET"], function=check_int)) + # ajax delete repos group perm user + m.connect('delete_repos_group_user_perm', + "/delete_repos_group_user_perm/{group_name:.*}", + action="delete_repos_group_user_perm", + conditions=dict(method=["DELETE"], function=check_group)) + + # ajax delete repos group perm users_group + m.connect('delete_repos_group_users_group_perm', + "/delete_repos_group_users_group_perm/{group_name:.*}", + action="delete_repos_group_users_group_perm", + conditions=dict(method=["DELETE"], function=check_group)) #ADMIN USER REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, @@ -310,8 +322,6 @@ def make_map(config): m.connect("formatted_notification", "/notifications/{notification_id}.{format}", action="show", conditions=dict(method=["GET"])) - - #ADMIN MAIN PAGES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/admin') as m: @@ -320,13 +330,12 @@ def make_map(config): action='add_repo') #========================================================================== - # API V1 + # API V2 #========================================================================== with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='api/api') as m: m.connect('api', '/api') - #USER JOURNAL rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal') @@ -388,11 +397,13 @@ def make_map(config): controller='changeset', revision='tip', conditions=dict(function=check_repo)) - rmap.connect('changeset_comment', '/{repo_name:.*}/changeset/{revision}/comment', + rmap.connect('changeset_comment', + '/{repo_name:.*}/changeset/{revision}/comment', controller='changeset', revision='tip', action='comment', conditions=dict(function=check_repo)) - rmap.connect('changeset_comment_delete', '/{repo_name:.*}/changeset/comment/{comment_id}/delete', + rmap.connect('changeset_comment_delete', + '/{repo_name:.*}/changeset/comment/{comment_id}/delete', controller='changeset', action='delete_comment', conditions=dict(function=check_repo, method=["DELETE"])) @@ -493,5 +504,4 @@ def make_map(config): controller='followers', action='followers', conditions=dict(function=check_repo)) - return rmap diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py index c2bcc29c..4e9e7b63 100644 --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -3,7 +3,7 @@ rhodecode.controllers.admin.repos ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Admin controller for RhodeCode + Repositories controller for RhodeCode :created_on: Apr 7, 2010 :author: marcink @@ -277,7 +277,6 @@ class ReposController(BaseController): return redirect(url('repos')) - @HasRepoPermissionAllDecorator('repository.admin') def delete_perm_user(self, repo_name): """ @@ -287,10 +286,11 @@ class ReposController(BaseController): """ try: - repo_model = RepoModel() - repo_model.delete_perm_user(request.POST, repo_name) + RepoModel().revoke_user_permission(repo=repo_name, + user=request.POST['user_id']) Session.commit() - except Exception, e: + except Exception: + log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of repository user'), category='error') raise HTTPInternalServerError() @@ -302,11 +302,14 @@ class ReposController(BaseController): :param repo_name: """ + try: - repo_model = RepoModel() - repo_model.delete_perm_users_group(request.POST, repo_name) + RepoModel().revoke_users_group_permission( + repo=repo_name, group_name=request.POST['users_group_id'] + ) Session.commit() - except Exception, e: + except Exception: + log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of repository' ' users groups'), category='error') @@ -321,8 +324,7 @@ class ReposController(BaseController): """ try: - repo_model = RepoModel() - repo_model.delete_stats(repo_name) + RepoModel().delete_stats(repo_name) Session.commit() except Exception, e: h.flash(_('An error occurred during deletion of repository stats'), diff --git a/rhodecode/controllers/admin/repos_groups.py b/rhodecode/controllers/admin/repos_groups.py index 38b870de..abfcc330 100644 --- a/rhodecode/controllers/admin/repos_groups.py +++ b/rhodecode/controllers/admin/repos_groups.py @@ -3,7 +3,7 @@ rhodecode.controllers.admin.repos_groups ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - repos groups controller for RhodeCode + Repositories groups controller for RhodeCode :created_on: Mar 23, 2010 :author: marcink @@ -29,19 +29,22 @@ import formencode from formencode import htmlfill -from pylons import request, response, session, tmpl_context as c, url -from pylons.controllers.util import abort, redirect +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 rhodecode.lib import helpers as h -from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator +from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\ + HasReposGroupPermissionAnyDecorator from rhodecode.lib.base import BaseController, render from rhodecode.model.db import RepoGroup from rhodecode.model.repos_group import ReposGroupModel from rhodecode.model.forms import ReposGroupForm from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel +from webob.exc import HTTPInternalServerError log = logging.getLogger(__name__) @@ -60,6 +63,10 @@ class ReposGroupsController(BaseController): c.repo_groups = RepoGroup.groups_choices() 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 @@ -74,13 +81,22 @@ class ReposGroupsController(BaseController): data['group_name'] = repo_group.name + # fill repository users + for p in repo_group.repo_group_to_perm: + data.update({'u_perm_%s' % p.user.username: + p.permission.permission_name}) + + # fill repository 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 @HasPermissionAnyDecorator('hg.admin') def index(self, format='html'): """GET /repos_groups: All items in the collection""" # url('repos_groups') - sk = lambda g: g.parents[0].group_name if g.parents else g.group_name c.groups = sorted(RepoGroup.query().all(), key=sk) return render('admin/repos_groups/repos_groups_show.html') @@ -94,7 +110,11 @@ class ReposGroupsController(BaseController): c.repo_groups_choices)() try: form_result = repos_group_form.to_python(dict(request.POST)) - ReposGroupModel().create(form_result) + ReposGroupModel().create( + group_name=form_result['group_name'], + group_description=form_result['group_description'], + parent=form_result['group_parent_id'] + ) Session.commit() h.flash(_('created repos group %s') \ % form_result['group_name'], category='success') @@ -134,10 +154,11 @@ class ReposGroupsController(BaseController): self.__load_defaults() c.repos_group = RepoGroup.get(id) - repos_group_form = ReposGroupForm(edit=True, - old_data=c.repos_group.get_dict(), - available_groups= - c.repo_groups_choices)() + repos_group_form = ReposGroupForm( + edit=True, + old_data=c.repos_group.get_dict(), + available_groups=c.repo_groups_choices + )() try: form_result = repos_group_form.to_python(dict(request.POST)) ReposGroupModel().update(id, form_result) @@ -201,10 +222,52 @@ class ReposGroupsController(BaseController): return redirect(url('repos_groups')) + @HasReposGroupPermissionAnyDecorator('group.admin') + def delete_repos_group_user_perm(self, group_name): + """ + DELETE an existing repositories group permission user + + :param group_name: + """ + + try: + ReposGroupModel().revoke_user_permission( + repos_group=group_name, user=request.POST['user_id'] + ) + Session.commit() + except Exception: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during deletion of group user'), + category='error') + raise HTTPInternalServerError() + + @HasReposGroupPermissionAnyDecorator('group.admin') + def delete_repos_group_users_group_perm(self, group_name): + """ + DELETE an existing repositories group permission users group + + :param group_name: + """ + + try: + ReposGroupModel().revoke_users_group_permission( + repos_group=group_name, + group_name=request.POST['users_group_id'] + ) + Session.commit() + except Exception: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during deletion of group' + ' users groups'), + category='error') + raise HTTPInternalServerError() + def show_by_name(self, group_name): id_ = RepoGroup.get_by_group_name(group_name).group_id return self.show(id_) + @HasReposGroupPermissionAnyDecorator('group.read', 'group.write', + 'group.admin') def show(self, id, format='html'): """GET /repos_groups/id: Show a specific item""" # url('repos_group', id=ID) @@ -240,7 +303,7 @@ class ReposGroupsController(BaseController): defaults = self.__load_data(id_) # we need to exclude this group from the group list for editing - c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups) + c.repo_groups = filter(lambda x: x[0] != id_, c.repo_groups) return htmlfill.render( render('admin/repos_groups/repos_groups_edit.html'), diff --git a/rhodecode/controllers/api/api.py b/rhodecode/controllers/api/api.py index 31e5dd45..e4669a89 100644 --- a/rhodecode/controllers/api/api.py +++ b/rhodecode/controllers/api/api.py @@ -401,13 +401,7 @@ class ApiController(JSONRPCController): for g in groups: group = RepoGroup.get_by_group_name(g) if not group: - group = ReposGroupModel().create( - dict( - group_name=g, - group_description='', - group_parent_id=parent_id - ) - ) + group = ReposGroupModel().create(g, '', parent_id) parent_id = group.group_id repo = RepoModel().create( @@ -434,11 +428,11 @@ class ApiController(JSONRPCController): raise JSONRPCError('failed to create repository %s' % repo_name) @HasPermissionAnyDecorator('hg.admin') - def add_user_to_repo(self, apiuser, repo_name, username, perm): + def grant_user_permission(self, repo_name, username, perm): """ - Add permission for a user to a repository + Grant permission for user on given repository, or update existing one + if found - :param apiuser: :param repo_name: :param username: :param perm: @@ -449,17 +443,15 @@ class ApiController(JSONRPCController): if repo is None: raise JSONRPCError('unknown repository %s' % repo) - try: - user = User.get_by_username(username) - except NoResultFound: - raise JSONRPCError('unknown user %s' % user) + user = User.get_by_username(username) + if user is None: + raise JSONRPCError('unknown user %s' % username) - RepositoryPermissionModel()\ - .update_or_delete_user_permission(repo, user, perm) - Session.commit() + RepoModel().grant_user_permission(repo=repo, user=user, perm=perm) + Session.commit() return dict( - msg='Added perm: %s for %s in repo: %s' % ( + msg='Granted perm: %s for user: %s in repo: %s' % ( perm, username, repo_name ) ) @@ -472,11 +464,45 @@ class ApiController(JSONRPCController): ) @HasPermissionAnyDecorator('hg.admin') - def add_users_group_to_repo(self, apiuser, repo_name, group_name, perm): + def revoke_user_permission(self, repo_name, username): """ - Add permission for a users group to a repository + Revoke permission for user on given repository + + :param repo_name: + :param username: + """ + + try: + repo = Repository.get_by_repo_name(repo_name) + if repo is None: + raise JSONRPCError('unknown repository %s' % repo) + + user = User.get_by_username(username) + if user is None: + raise JSONRPCError('unknown user %s' % username) + + RepoModel().revoke_user_permission(repo=repo_name, user=username) + + Session.commit() + return dict( + msg='Revoked perm for user: %s in repo: %s' % ( + username, repo_name + ) + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission %(repo)s for %(user)s' % dict( + user=username, repo=repo_name + ) + ) + + @HasPermissionAnyDecorator('hg.admin') + def grant_users_group_permission(self, repo_name, group_name, perm): + """ + Grant permission for users group on given repository, or update + existing one if found - :param apiuser: :param repo_name: :param group_name: :param perm: @@ -487,24 +513,59 @@ class ApiController(JSONRPCController): if repo is None: raise JSONRPCError('unknown repository %s' % repo) - try: - user_group = UsersGroup.get_by_group_name(group_name) - except NoResultFound: + user_group = UsersGroup.get_by_group_name(group_name) + if user_group is None: raise JSONRPCError('unknown users group %s' % user_group) - RepositoryPermissionModel()\ - .update_or_delete_users_group_permission(repo, user_group, - perm) + RepoModel().grant_users_group_permission(repo=repo_name, + group_name=group_name, + perm=perm) + Session.commit() return dict( - msg='Added perm: %s for %s in repo: %s' % ( + msg='Granted perm: %s for group: %s in repo: %s' % ( perm, group_name, repo_name ) ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( - 'failed to edit permission %(repo)s for %(usergr)s' % dict( - usergr=group_name, repo=repo_name + 'failed to edit permission %(repo)s for %(usersgr)s' % dict( + usersgr=group_name, repo=repo_name + ) + ) + + @HasPermissionAnyDecorator('hg.admin') + def revoke_users_group_permission(self, repo_name, group_name): + """ + Revoke permission for users group on given repository + + :param repo_name: + :param group_name: + """ + + try: + repo = Repository.get_by_repo_name(repo_name) + if repo is None: + raise JSONRPCError('unknown repository %s' % repo) + + user_group = UsersGroup.get_by_group_name(group_name) + if user_group is None: + raise JSONRPCError('unknown users group %s' % user_group) + + RepoModel().revoke_users_group_permission(repo=repo_name, + group_name=group_name) + + Session.commit() + return dict( + msg='Revoked perm for group: %s in repo: %s' % ( + group_name, repo_name + ) + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission %(repo)s for %(usersgr)s' % dict( + usersgr=group_name, repo=repo_name ) ) diff --git a/rhodecode/controllers/home.py b/rhodecode/controllers/home.py index 9129a89c..77ef18eb 100644 --- a/rhodecode/controllers/home.py +++ b/rhodecode/controllers/home.py @@ -30,7 +30,7 @@ from paste.httpexceptions import HTTPBadRequest from rhodecode.lib.auth import LoginRequired from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import RepoGroup, Repository +from rhodecode.model.db import Repository log = logging.getLogger(__name__) @@ -42,11 +42,8 @@ class HomeController(BaseController): super(HomeController, self).__before__() def index(self): - c.repos_list = self.scm_model.get_repos() - - c.groups = RepoGroup.query()\ - .filter(RepoGroup.group_parent_id == None).all() + c.groups = self.scm_model.get_repos_groups() return render('/index.html') diff --git a/rhodecode/lib/__init__.py b/rhodecode/lib/__init__.py index 9a194782..2e2b05c5 100644 --- a/rhodecode/lib/__init__.py +++ b/rhodecode/lib/__init__.py @@ -25,6 +25,8 @@ import os import re +from vcs.utils.lazy import LazyProperty + def __get_lem(): from pygments import lexers @@ -213,6 +215,7 @@ def safe_unicode(str_, from_encoding='utf8'): except (ImportError, UnicodeDecodeError, Exception): return unicode(str_, from_encoding, 'replace') + def safe_str(unicode_, to_encoding='utf8'): """ safe str function. Does few trick to turn unicode_ into string @@ -250,7 +253,6 @@ def safe_str(unicode_, to_encoding='utf8'): return safe_str - def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs): """ Custom engine_from_config functions that makes sure we use NullPool for @@ -393,6 +395,7 @@ def credentials_filter(uri): return ''.join(uri) + def get_changeset_safe(repo, rev): """ Safe version of get_changeset if this changeset doesn't exists for a @@ -437,6 +440,7 @@ def get_current_revision(quiet=False): "was: %s" % err) return None + def extract_mentioned_users(s): """ Returns unique usernames from given string s that have @mention diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py index 21b457b2..7cd402c9 100644 --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -31,7 +31,7 @@ import hashlib from tempfile import _RandomNameSequence from decorator import decorator -from pylons import config, session, url, request +from pylons import config, url, request from pylons.controllers.util import abort, redirect from pylons.i18n.translation import _ @@ -45,7 +45,7 @@ if __platform__ in PLATFORM_OTHERS: from rhodecode.lib import str2bool, safe_unicode from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError -from rhodecode.lib.utils import get_repo_slug +from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug from rhodecode.lib.auth_ldap import AuthLdap from rhodecode.model import meta @@ -80,8 +80,8 @@ class PasswordGenerator(object): def __init__(self, passwd=''): self.passwd = passwd - def gen_password(self, len, type): - self.passwd = ''.join([random.choice(type) for _ in xrange(len)]) + def gen_password(self, length, type_): + self.passwd = ''.join([random.choice(type_) for _ in xrange(length)]) return self.passwd @@ -575,6 +575,41 @@ class HasRepoPermissionAnyDecorator(PermsDecorator): return False +class HasReposGroupPermissionAllDecorator(PermsDecorator): + """ + Checks for access permission for all given predicates for specific + repository. All of them have to be meet in order to fulfill the request + """ + + def check_permissions(self): + group_name = get_repos_group_slug(request) + try: + user_perms = set([self.user_perms['repositories_groups'][group_name]]) + except KeyError: + return False + if self.required_perms.issubset(user_perms): + return True + return False + + +class HasReposGroupPermissionAnyDecorator(PermsDecorator): + """ + Checks for access permission for any of given predicates for specific + repository. In order to fulfill the request any of predicates must be meet + """ + + def check_permissions(self): + group_name = get_repos_group_slug(request) + + try: + user_perms = set([self.user_perms['repositories_groups'][group_name]]) + except KeyError: + return False + if self.required_perms.intersection(user_perms): + return True + return False + + #============================================================================== # CHECK FUNCTIONS #============================================================================== @@ -641,8 +676,9 @@ class HasRepoPermissionAll(PermsFunction): self.repo_name = get_repo_slug(request) try: - self.user_perms = set([self.user_perms['reposit' - 'ories'][self.repo_name]]) + self.user_perms = set( + [self.user_perms['repositories'][self.repo_name]] + ) except KeyError: return False self.granted_for = self.repo_name @@ -662,8 +698,9 @@ class HasRepoPermissionAny(PermsFunction): self.repo_name = get_repo_slug(request) try: - self.user_perms = set([self.user_perms['reposi' - 'tories'][self.repo_name]]) + self.user_perms = set( + [self.user_perms['repositories'][self.repo_name]] + ) except KeyError: return False self.granted_for = self.repo_name @@ -672,6 +709,42 @@ class HasRepoPermissionAny(PermsFunction): return False +class HasReposGroupPermissionAny(PermsFunction): + def __call__(self, group_name=None, check_Location=''): + self.group_name = group_name + return super(HasReposGroupPermissionAny, self).__call__(check_Location) + + def check_permissions(self): + try: + self.user_perms = set( + [self.user_perms['repositories_groups'][self.group_name]] + ) + except KeyError: + return False + self.granted_for = self.repo_name + if self.required_perms.intersection(self.user_perms): + return True + return False + + +class HasReposGroupPermissionAll(PermsFunction): + def __call__(self, group_name=None, check_Location=''): + self.group_name = group_name + return super(HasReposGroupPermissionAny, self).__call__(check_Location) + + def check_permissions(self): + try: + self.user_perms = set( + [self.user_perms['repositories_groups'][self.group_name]] + ) + except KeyError: + return False + self.granted_for = self.repo_name + if self.required_perms.issubset(self.user_perms): + return True + return False + + #============================================================================== # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH #============================================================================== diff --git a/rhodecode/lib/db_manage.py b/rhodecode/lib/db_manage.py index 4dbd390b..fc4ed3c5 100644 --- a/rhodecode/lib/db_manage.py +++ b/rhodecode/lib/db_manage.py @@ -442,23 +442,28 @@ class DbManage(object): def create_permissions(self): # module.(access|create|change|delete)_[name] - # module.(read|write|owner) - perms = [('repository.none', 'Repository no access'), - ('repository.read', 'Repository read access'), - ('repository.write', 'Repository write access'), - ('repository.admin', 'Repository admin access'), - ('hg.admin', 'Hg Administrator'), - ('hg.create.repository', 'Repository create'), - ('hg.create.none', 'Repository creation disabled'), - ('hg.register.none', 'Register disabled'), - ('hg.register.manual_activate', 'Register new user with ' - 'RhodeCode without manual' - 'activation'), - - ('hg.register.auto_activate', 'Register new user with ' - 'RhodeCode without auto ' - 'activation'), - ] + # module.(none|read|write|admin) + perms = [ + ('repository.none', 'Repository no access'), + ('repository.read', 'Repository read access'), + ('repository.write', 'Repository write access'), + ('repository.admin', 'Repository admin access'), + + ('group.none', 'Repositories Group no access'), + ('group.read', 'Repositories Group read access'), + ('group.write', 'Repositories Group write access'), + ('group.admin', 'Repositories Group admin access'), + + ('hg.admin', 'Hg Administrator'), + ('hg.create.repository', 'Repository create'), + ('hg.create.none', 'Repository creation disabled'), + ('hg.register.none', 'Register disabled'), + ('hg.register.manual_activate', 'Register new user with RhodeCode ' + 'without manual activation'), + + ('hg.register.auto_activate', 'Register new user with RhodeCode ' + 'without auto activation'), + ] for p in perms: new_perm = Permission() diff --git a/rhodecode/lib/hooks.py b/rhodecode/lib/hooks.py index ed327c0c..04126d24 100644 --- a/rhodecode/lib/hooks.py +++ b/rhodecode/lib/hooks.py @@ -130,7 +130,7 @@ def log_create_repository(repository_dict, created_by, **kwargs): Post create repository Hook. This is a dummy function for admins to re-use if needed - :param repository: dict dump of repository object + :param repository: dict dump of repository object :param created_by: username who created repository :param created_date: date of creation @@ -152,4 +152,4 @@ def log_create_repository(repository_dict, created_by, **kwargs): """ - return 0
\ No newline at end of file + return 0 diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py index aeb3b49b..468a2786 100644 --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -52,6 +52,7 @@ from rhodecode.model import meta from rhodecode.model.db import Repository, User, RhodeCodeUi, \ UserLog, RepoGroup, RhodeCodeSetting from rhodecode.model.meta import Session +from rhodecode.model.repos_group import ReposGroupModel log = logging.getLogger(__name__) @@ -94,6 +95,10 @@ def get_repo_slug(request): return request.environ['pylons.routes_dict'].get('repo_name') +def get_repos_group_slug(request): + return request.environ['pylons.routes_dict'].get('group_name') + + def action_logger(user, action, repo, ipaddr='', sa=None, commit=False): """ Action logger for various actions made by users @@ -197,6 +202,7 @@ def is_valid_repo(repo_name, base_path): except VCSError: return False + def is_valid_repos_group(repos_group_name, base_path): """ Returns True if given path is a repos group False otherwise @@ -216,6 +222,7 @@ def is_valid_repos_group(repos_group_name, base_path): return False + def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while True: ok = raw_input(prompt) @@ -317,7 +324,8 @@ class EmptyChangeset(BaseChangeset): an EmptyChangeset """ - def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None): + def __init__(self, cs='0' * 40, repo=None, requested_revision=None, + alias=None): self._empty_cs = cs self.revision = -1 self.message = '' @@ -368,14 +376,23 @@ def map_groups(groups): # last element is repo in nested groups structure groups = groups[:-1] - + rgm = ReposGroupModel(sa) for lvl, group_name in enumerate(groups): + log.debug('creating group level: %s group_name: %s' % (lvl, group_name)) group_name = '/'.join(groups[:lvl] + [group_name]) - group = sa.query(RepoGroup).filter(RepoGroup.group_name == group_name).scalar() + group = RepoGroup.get_by_group_name(group_name) + desc = '%s group' % group_name + +# # WTF that doesn't work !? +# if group is None: +# group = rgm.create(group_name, desc, parent, just_db=True) +# sa.commit() if group is None: group = RepoGroup(group_name, parent) + group.group_description = desc sa.add(group) + rgm._create_default_perms(group) sa.commit() parent = group return group @@ -404,15 +421,14 @@ def repo2db_mapper(initial_repo_list, remove_obsolete=False): log.info('repository %s not found creating default' % name) added.append(name) form_data = { - 'repo_name': name, - 'repo_name_full': name, - 'repo_type': repo.alias, - 'description': repo.description \ - if repo.description != 'unknown' else \ - '%s repository' % name, - 'private': False, - 'group_id': getattr(group, 'group_id', None) - } + 'repo_name': name, + 'repo_name_full': name, + 'repo_type': repo.alias, + 'description': repo.description \ + if repo.description != 'unknown' else '%s repository' % name, + 'private': False, + 'group_id': getattr(group, 'group_id', None) + } rm.create(form_data, user, just_db=True) sa.commit() removed = [] @@ -426,6 +442,7 @@ def repo2db_mapper(initial_repo_list, remove_obsolete=False): return added, removed + # set cache regions for beaker so celery can utilise it def add_cache(settings): cache_settings = {'regions': None} diff --git a/rhodecode/model/__init__.py b/rhodecode/model/__init__.py index 5c435a2b..b11325d2 100644 --- a/rhodecode/model/__init__.py +++ b/rhodecode/model/__init__.py @@ -74,12 +74,13 @@ class BaseModel(object): else: self.sa = meta.Session - def _get_instance(self, cls, instance): + def _get_instance(self, cls, instance, callback=None): """ - Get's instance of given cls using some simple lookup mechanism + Get's instance of given cls using some simple lookup mechanism. :param cls: class to fetch :param instance: int or Instance + :param callback: callback to call if all lookups failed """ if isinstance(instance, cls): @@ -88,5 +89,10 @@ class BaseModel(object): return cls.get(instance) else: if instance: - raise Exception('given object must be int or Instance' - ' of %s got %s' % (type(cls), type(instance))) + if callback is None: + raise Exception( + 'given object must be int or Instance of %s got %s, ' + 'no callback provided' % (cls, type(instance)) + ) + else: + return callback(instance) diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py index d5165d45..31829f45 100755 --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -717,6 +717,9 @@ class RepoGroup(Base, BaseModel): group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') + users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all') + parent_group = relationship('RepoGroup', remote_side=group_id) def __init__(self, group_name='', parent_group=None): @@ -833,8 +836,9 @@ class Permission(Base, BaseModel): permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) def __repr__(self): - return "<%s('%s:%s')>" % (self.__class__.__name__, - self.permission_id, self.permission_name) + return "<%s('%s:%s')>" % ( + self.__class__.__name__, self.permission_id, self.permission_name + ) @classmethod def get_by_key(cls, key): @@ -843,9 +847,18 @@ class Permission(Base, BaseModel): @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) + .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() diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py index 528936fe..b2379e6a 100644 --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -388,57 +388,66 @@ def ValidForkType(old_data): return _ValidForkType -class ValidPerms(formencode.validators.FancyValidator): - messages = {'perm_new_member_name': _('This username or users group name' - ' is not valid')} +def ValidPerms(type_='repo'): + if type_ == 'group': + EMPTY_PERM = 'group.none' + elif type_ == 'repo': + EMPTY_PERM = 'repository.none' + + class _ValidPerms(formencode.validators.FancyValidator): + messages = { + 'perm_new_member_name': + _('This username or users group name is not valid') + } - def to_python(self, value, state): - perms_update = [] - perms_new = [] - #build a list of permission to update and new permission to create - for k, v in value.items(): - #means new added member to permissions - if k.startswith('perm_new_member'): - new_perm = value.get('perm_new_member', False) - new_member = value.get('perm_new_member_name', False) - new_type = value.get('perm_new_member_type') - - if new_member and new_perm: - if (new_member, new_perm, new_type) not in perms_new: - perms_new.append((new_member, new_perm, new_type)) - elif k.startswith('u_perm_') or k.startswith('g_perm_'): - member = k[7:] - t = {'u': 'user', - 'g': 'users_group' - }[k[0]] - if member == 'default': - if value['private']: - #set none for default when updating to private repo - v = 'repository.none' - perms_update.append((member, v, t)) - - value['perms_updates'] = perms_update - value['perms_new'] = perms_new - - #update permissions - for k, v, t in perms_new: - try: - if t is 'user': - self.user_db = User.query()\ - .filter(User.active == True)\ - .filter(User.username == k).one() - if t is 'users_group': - self.user_db = UsersGroup.query()\ - .filter(UsersGroup.users_group_active == True)\ - .filter(UsersGroup.users_group_name == k).one() - - except Exception: - msg = self.message('perm_new_member_name', - state=State_obj) - raise formencode.Invalid( - msg, value, state, error_dict={'perm_new_member_name': msg} - ) - return value + def to_python(self, value, state): + perms_update = [] + perms_new = [] + # build a list of permission to update and new permission to create + for k, v in value.items(): + # means new added member to permissions + if k.startswith('perm_new_member'): + new_perm = value.get('perm_new_member', False) + new_member = value.get('perm_new_member_name', False) + new_type = value.get('perm_new_member_type') + + if new_member and new_perm: + if (new_member, new_perm, new_type) not in perms_new: + perms_new.append((new_member, new_perm, new_type)) + elif k.startswith('u_perm_') or k.startswith('g_perm_'): + member = k[7:] + t = {'u': 'user', + 'g': 'users_group' + }[k[0]] + if member == 'default': + if value.get('private'): + # set none for default when updating to private repo + v = EMPTY_PERM + perms_update.append((member, v, t)) + + value['perms_updates'] = perms_update + value['perms_new'] = perms_new + + # update permissions + for k, v, t in perms_new: + try: + if t is 'user': + self.user_db = User.query()\ + .filter(User.active == True)\ + .filter(User.username == k).one() + if t is 'users_group': + self.user_db = UsersGroup.query()\ + .filter(UsersGroup.users_group_active == True)\ + .filter(UsersGroup.users_group_name == k).one() + + except Exception: + msg = self.message('perm_new_member_name', + state=State_obj) + raise formencode.Invalid( + msg, value, state, error_dict={'perm_new_member_name': msg} + ) + return value + return _ValidPerms class ValidSettings(formencode.validators.FancyValidator): @@ -588,7 +597,7 @@ def UsersGroupForm(edit=False, old_data={}, available_members=[]): def ReposGroupForm(edit=False, old_data={}, available_groups=[]): class _ReposGroupForm(formencode.Schema): allow_extra_fields = True - filter_extra_fields = True + filter_extra_fields = False group_name = All(UnicodeString(strip=True, min=1, not_empty=True), SlugifyName()) @@ -598,7 +607,7 @@ def ReposGroupForm(edit=False, old_data={}, available_groups=[]): testValueList=True, if_missing=None, not_empty=False) - chained_validators = [ValidReposGroup(edit, old_data)] + chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')] return _ReposGroupForm @@ -649,7 +658,7 @@ def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), #this is repo owner user = All(UnicodeString(not_empty=True), ValidRepoUser) - chained_validators = [ValidRepoName(edit, old_data), ValidPerms] + chained_validators = [ValidRepoName(edit, old_data), ValidPerms()] return _RepoForm @@ -683,7 +692,7 @@ def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys() repo_group = OneOf(repo_groups, hideList=True) private = StringBoolean(if_missing=False) - chained_validators = [ValidRepoName(edit, old_data), ValidPerms, + chained_validators = [ValidRepoName(edit, old_data), ValidPerms(), ValidSettings] return _RepoForm diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py index bb2f950a..dc5617e3 100644 --- a/rhodecode/model/notification.py +++ b/rhodecode/model/notification.py @@ -42,10 +42,7 @@ log = logging.getLogger(__name__) class NotificationModel(BaseModel): def __get_user(self, user): - if isinstance(user, basestring): - return User.get_by_username(username=user) - else: - return self._get_instance(User, user) + return self._get_instance(User, user, callback=User.get_by_username) def __get_notification(self, notification): if isinstance(notification, Notification): diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py index 6650de01..99ac4788 100644 --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -28,9 +28,9 @@ import logging import traceback from datetime import datetime -from vcs.utils.lazy import LazyProperty from vcs.backends import get_backend +from rhodecode.lib import LazyProperty from rhodecode.lib import safe_str, safe_unicode from rhodecode.lib.caching_query import FromCache from rhodecode.lib.hooks import log_create_repository @@ -39,11 +39,31 @@ from rhodecode.model import BaseModel from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \ Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup + log = logging.getLogger(__name__) class RepoModel(BaseModel): + def __get_user(self, user): + return self._get_instance(User, user, callback=User.get_by_username) + + def __get_users_group(self, users_group): + return self._get_instance(UsersGroup, users_group, + callback=UsersGroup.get_by_group_name) + + def __get_repos_group(self, repos_group): + return self._get_instance(RepoGroup, repos_group, + callback=RepoGroup.get_by_group_name) + + def __get_repo(self, repository): + return self._get_instance(Repository, repository, + callback=Repository.get_by_repo_name) + + def __get_perm(self, permission): + return self._get_instance(Permission, permission, + callback=Permission.get_by_key) + @LazyProperty def repos_path(self): """ @@ -138,49 +158,24 @@ class RepoModel(BaseModel): # update permissions for member, perm, member_type in form_data['perms_updates']: if member_type == 'user': - _member = User.get_by_username(member) - r2p = self.sa.query(UserRepoToPerm)\ - .filter(UserRepoToPerm.user == _member)\ - .filter(UserRepoToPerm.repository == cur_repo)\ - .one() - - r2p.permission = self.sa.query(Permission)\ - .filter(Permission.permission_name == - perm).scalar() - self.sa.add(r2p) + # this updates existing one + RepoModel().grant_user_permission( + repo=cur_repo, user=member, perm=perm + ) else: - g2p = self.sa.query(UsersGroupRepoToPerm)\ - .filter(UsersGroupRepoToPerm.users_group == - UsersGroup.get_by_group_name(member))\ - .filter(UsersGroupRepoToPerm.repository == - cur_repo).one() - - g2p.permission = self.sa.query(Permission)\ - .filter(Permission.permission_name == - perm).scalar() - self.sa.add(g2p) - + RepoModel().grant_users_group_permission( + repo=cur_repo, group_name=member, perm=perm + ) # set new permissions for member, perm, member_type in form_data['perms_new']: if member_type == 'user': - r2p = UserRepoToPerm() - r2p.repository = cur_repo - r2p.user = User.get_by_username(member) - - r2p.permission = self.sa.query(Permission)\ - .filter(Permission. - permission_name == perm)\ - .scalar() - self.sa.add(r2p) + RepoModel().grant_user_permission( + repo=cur_repo, user=member, perm=perm + ) else: - g2p = UsersGroupRepoToPerm() - g2p.repository = cur_repo - g2p.users_group = UsersGroup.get_by_group_name(member) - g2p.permission = self.sa.query(Permission)\ - .filter(Permission. - permission_name == perm)\ - .scalar() - self.sa.add(g2p) + RepoModel().grant_users_group_permission( + repo=cur_repo, group_name=member, perm=perm + ) # update current repo for k, v in form_data.items(): @@ -314,28 +309,93 @@ class RepoModel(BaseModel): log.error(traceback.format_exc()) raise - def delete_perm_user(self, form_data, repo_name): - try: - obj = self.sa.query(UserRepoToPerm)\ - .filter(UserRepoToPerm.repository \ - == self.get_by_repo_name(repo_name))\ - .filter(UserRepoToPerm.user_id == form_data['user_id']).one() - self.sa.delete(obj) - except: - log.error(traceback.format_exc()) - raise + def grant_user_permission(self, repo, user, perm): + """ + Grant permission for user on given repository, or update existing one + if found - def delete_perm_users_group(self, form_data, repo_name): - try: - obj = self.sa.query(UsersGroupRepoToPerm)\ - .filter(UsersGroupRepoToPerm.repository \ - == self.get_by_repo_name(repo_name))\ - .filter(UsersGroupRepoToPerm.users_group_id - == form_data['users_group_id']).one() - self.sa.delete(obj) - except: - log.error(traceback.format_exc()) - raise + :param repo: Instance of Repository, repository_id, or repository name + :param user: Instance of User, user_id or username + :param perm: Instance of Permission, or permission_name + """ + user = self.__get_user(user) + repo = self.__get_repo(repo) + 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)\ + .scalar() + if obj is None: + # create new ! + obj = UserRepoToPerm() + obj.repository = repo + obj.user = user + obj.permission = permission + self.sa.add(obj) + + def revoke_user_permission(self, repo, user): + """ + Revoke permission for user on given repository + + :param repo: Instance of Repository, repository_id, or repository name + :param user: Instance of User, user_id or username + """ + user = self.__get_user(user) + repo = self.__get_repo(repo) + + obj = self.sa.query(UserRepoToPerm)\ + .filter(UserRepoToPerm.repository == repo)\ + .filter(UserRepoToPerm.user == user)\ + .one() + self.sa.delete(obj) + + def grant_users_group_permission(self, repo, group_name, perm): + """ + Grant permission for users group on given repository, or update + existing one if found + + :param repo: Instance of Repository, repository_id, or repository name + :param group_name: Instance of UserGroup, users_group_id, + or users group name + :param perm: Instance of Permission, or permission_name + """ + repo = self.__get_repo(repo) + group_name = self.__get_users_group(group_name) + permission = self.__get_perm(perm) + + # check if we have that permission already + obj = self.sa.query(UsersGroupRepoToPerm)\ + .filter(UsersGroupRepoToPerm.users_group == group_name)\ + .filter(UsersGroupRepoToPerm.repository == repo)\ + .scalar() + + if obj is None: + # create new + obj = UsersGroupRepoToPerm() + + obj.repository = repo + obj.users_group = group_name + obj.permission = permission + self.sa.add(obj) + + def revoke_users_group_permission(self, repo, group_name): + """ + Revoke permission for users group on given repository + + :param repo: Instance of Repository, repository_id, or repository name + :param group_name: Instance of UserGroup, users_group_id, + or users group name + """ + repo = self.__get_repo(repo) + group_name = self.__get_users_group(group_name) + + obj = self.sa.query(UsersGroupRepoToPerm)\ + .filter(UsersGroupRepoToPerm.repository == repo)\ + .filter(UsersGroupRepoToPerm.users_group == group_name)\ + .one() + self.sa.delete(obj) def delete_stats(self, repo_name): """ @@ -345,8 +405,9 @@ class RepoModel(BaseModel): """ try: obj = self.sa.query(Statistics)\ - .filter(Statistics.repository == \ - self.get_by_repo_name(repo_name)).one() + .filter(Statistics.repository == + self.get_by_repo_name(repo_name))\ + .one() self.sa.delete(obj) except: log.error(traceback.format_exc()) @@ -373,10 +434,9 @@ class RepoModel(BaseModel): new_parent_path = '' # we need to make it str for mercurial - repo_path = os.path.join(*map(lambda x:safe_str(x), + repo_path = os.path.join(*map(lambda x: safe_str(x), [self.repos_path, new_parent_path, repo_name])) - # check if this path is not a repository if is_valid_repo(repo_path, self.repos_path): raise Exception('This path %s is a valid repository' % repo_path) @@ -393,7 +453,6 @@ class RepoModel(BaseModel): backend(repo_path, create=True, src_url=clone_uri) - def __rename_repo(self, old, new): """ renames repository on filesystem @@ -406,8 +465,9 @@ class RepoModel(BaseModel): old_path = os.path.join(self.repos_path, old) new_path = os.path.join(self.repos_path, new) if os.path.isdir(new_path): - raise Exception('Was trying to rename to already existing dir %s' \ - % new_path) + raise Exception( + 'Was trying to rename to already existing dir %s' % new_path + ) shutil.move(old_path, new_path) def __delete_repo(self, repo): @@ -426,7 +486,6 @@ class RepoModel(BaseModel): shutil.move(os.path.join(rm_path, '.%s' % alias), os.path.join(rm_path, 'rm__.%s' % alias)) # disable repo - shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \ - % (datetime.today()\ - .strftime('%Y%m%d_%H%M%S_%f'), - repo.repo_name))) + _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'), + repo.repo_name) + shutil.move(rm_path, os.path.join(self.repos_path, _d)) diff --git a/rhodecode/model/repo_permission.py b/rhodecode/model/repo_permission.py index 7a9cc8e2..0f19ef56 100644 --- a/rhodecode/model/repo_permission.py +++ b/rhodecode/model/repo_permission.py @@ -26,14 +26,29 @@ import logging from rhodecode.model import BaseModel -from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission +from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission,\ + User, Repository log = logging.getLogger(__name__) class RepositoryPermissionModel(BaseModel): + def __get_user(self, user): + return self._get_instance(User, user, callback=User.get_by_username) + + def __get_repo(self, repository): + return self._get_instance(Repository, repository, + callback=Repository.get_by_repo_name) + + def __get_perm(self, permission): + return self._get_instance(Permission, permission, + callback=Permission.get_by_key) + def get_user_permission(self, repository, user): + repository = self.__get_repo(repository) + user = self.__get_user(user) + return UserRepoToPerm.query() \ .filter(UserRepoToPerm.user == user) \ .filter(UserRepoToPerm.repository == repository) \ diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py index e0de7076..94590ccb 100644 --- a/rhodecode/model/repos_group.py +++ b/rhodecode/model/repos_group.py @@ -28,18 +28,32 @@ import logging import traceback import shutil -from pylons.i18n.translation import _ - -from vcs.utils.lazy import LazyProperty +from rhodecode.lib import LazyProperty from rhodecode.model import BaseModel -from rhodecode.model.db import RepoGroup, RhodeCodeUi +from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \ + User, Permission, UsersGroupRepoGroupToPerm, UsersGroup log = logging.getLogger(__name__) class ReposGroupModel(BaseModel): + def __get_user(self, user): + return self._get_instance(User, user, callback=User.get_by_username) + + def __get_users_group(self, users_group): + return self._get_instance(UsersGroup, users_group, + callback=UsersGroup.get_by_group_name) + + def __get_repos_group(self, repos_group): + return self._get_instance(RepoGroup, repos_group, + callback=RepoGroup.get_by_group_name) + + def __get_perm(self, permission): + return self._get_instance(Permission, permission, + callback=Permission.get_by_key) + @LazyProperty def repos_path(self): """ @@ -49,6 +63,24 @@ class ReposGroupModel(BaseModel): q = RhodeCodeUi.get_by_key('/').one() return q.ui_value + def _create_default_perms(self, new_group): + # create default permission + repo_group_to_perm = UserRepoGroupToPerm() + default_perm = 'group.read' + for p in User.get_by_username('default').user_perms: + if p.permission.permission_name.startswith('group.'): + default_perm = p.permission.permission_name + break + + repo_group_to_perm.permission_id = self.sa.query(Permission)\ + .filter(Permission.permission_name == default_perm)\ + .one().permission_id + + repo_group_to_perm.group = new_group + repo_group_to_perm.user_id = User.get_by_username('default').user_id + + self.sa.add(repo_group_to_perm) + def __create_group(self, group_name): """ makes repositories group on filesystem @@ -102,16 +134,21 @@ class ReposGroupModel(BaseModel): # delete only if that path really exists os.rmdir(rm_path) - def create(self, form_data): + def create(self, group_name, group_description, parent, just_db=False): try: new_repos_group = RepoGroup() - new_repos_group.group_description = form_data['group_description'] - new_repos_group.parent_group = RepoGroup.get(form_data['group_parent_id']) - new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name']) + new_repos_group.group_description = group_description + new_repos_group.parent_group = self.__get_repos_group(parent) + new_repos_group.group_name = new_repos_group.get_new_name(group_name) self.sa.add(new_repos_group) - self.sa.flush() - self.__create_group(new_repos_group.group_name) + self._create_default_perms(new_repos_group) + + 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: @@ -122,6 +159,29 @@ class ReposGroupModel(BaseModel): try: repos_group = RepoGroup.get(repos_group_id) + + # update permissions + for member, perm, member_type in form_data['perms_updates']: + if member_type == 'user': + # this updates also current one if found + ReposGroupModel().grant_user_permission( + repos_group=repos_group, user=member, perm=perm + ) + else: + ReposGroupModel().grant_users_group_permission( + repos_group=repos_group, group_name=member, perm=perm + ) + # set new permissions + for member, perm, member_type in form_data['perms_new']: + if member_type == 'user': + ReposGroupModel().grant_user_permission( + repos_group=repos_group, user=member, perm=perm + ) + else: + ReposGroupModel().grant_users_group_permission( + repos_group=repos_group, group_name=member, perm=perm + ) + old_path = repos_group.full_path # change properties @@ -154,3 +214,97 @@ class ReposGroupModel(BaseModel): except: log.error(traceback.format_exc()) raise + + def grant_user_permission(self, repos_group, user, perm): + """ + Grant permission for user on given repositories 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_repos_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) + + def revoke_user_permission(self, repos_group, user): + """ + Revoke permission for user on given repositories 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_repos_group(repos_group) + user = self.__get_user(user) + + obj = self.sa.query(UserRepoGroupToPerm)\ + .filter(UserRepoGroupToPerm.user == user)\ + .filter(UserRepoGroupToPerm.group == repos_group)\ + .one() + self.sa.delete(obj) + + def grant_users_group_permission(self, repos_group, group_name, perm): + """ + Grant permission for users group on given repositories 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 users group name + :param perm: Instance of Permission, or permission_name + """ + repos_group = self.__get_repos_group(repos_group) + group_name = self.__get_users_group(group_name) + permission = self.__get_perm(perm) + + # check if we have that permission already + obj = self.sa.query(UsersGroupRepoGroupToPerm)\ + .filter(UsersGroupRepoGroupToPerm.group == repos_group)\ + .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\ + .scalar() + + if obj is None: + # create new + obj = UsersGroupRepoGroupToPerm() + + obj.group = repos_group + obj.users_group = group_name + obj.permission = permission + self.sa.add(obj) + + def revoke_users_group_permission(self, repos_group, group_name): + """ + Revoke permission for users group on given repositories group + + :param repos_group: Instance of ReposGroup, repositories_group_id, + or repositories_group name + :param group_name: Instance of UserGroup, users_group_id, + or users group name + """ + repos_group = self.__get_repos_group(repos_group) + group_name = self.__get_users_group(group_name) + + obj = self.sa.query(UsersGroupRepoGroupToPerm)\ + .filter(UsersGroupRepoGroupToPerm.group == repos_group)\ + .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\ + .one() + self.sa.delete(obj) diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py index 668804f8..5542ab3b 100644 --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -36,12 +36,12 @@ from vcs.nodes import FileNode from rhodecode import BACKENDS from rhodecode.lib import helpers as h from rhodecode.lib import safe_str -from rhodecode.lib.auth import HasRepoPermissionAny +from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \ action_logger, EmptyChangeset from rhodecode.model import BaseModel from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \ - UserFollowing, UserLog, User + UserFollowing, UserLog, User, RepoGroup log = logging.getLogger(__name__) @@ -80,15 +80,16 @@ class CachedRepoList(object): for dbr in self.db_repo_list: scmr = dbr.scm_instance_cached # check permission at this level - if not HasRepoPermissionAny('repository.read', 'repository.write', - 'repository.admin')(dbr.repo_name, - 'get repo check'): + if not HasRepoPermissionAny( + 'repository.read', 'repository.write', 'repository.admin' + )(dbr.repo_name, 'get repo check'): continue if scmr is None: - log.error('%s this repository is present in database but it ' - 'cannot be created as an scm instance', - dbr.repo_name) + log.error( + '%s this repository is present in database but it ' + 'cannot be created as an scm instance' % dbr.repo_name + ) continue last_change = scmr.last_change @@ -115,6 +116,28 @@ class CachedRepoList(object): yield tmp_d +class GroupList(object): + + def __init__(self, db_repo_group_list): + self.db_repo_group_list = db_repo_group_list + + def __len__(self): + return len(self.db_repo_group_list) + + def __repr__(self): + return '<%s (%s)>' % (self.__class__.__name__, self.__len__()) + + def __iter__(self): + for dbgr in self.db_repo_group_list: + # check permission at this level + if not HasReposGroupPermissionAny( + 'group.read', 'group.write', 'group.admin' + )(dbgr.group_name, 'get group repo check'): + continue + + yield dbgr + + class ScmModel(BaseModel): """ Generic Scm Model @@ -200,6 +223,14 @@ class ScmModel(BaseModel): return repo_iter + def get_repos_groups(self, all_groups=None): + if all_groups is None: + all_groups = RepoGroup.query()\ + .filter(RepoGroup.group_parent_id == None).all() + group_iter = GroupList(all_groups) + + return group_iter + def mark_for_invalidation(self, repo_name): """Puts cache invalidation task into db for further global cache invalidation diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py index 59dbc871..cedce660 100644 --- a/rhodecode/model/user.py +++ b/rhodecode/model/user.py @@ -35,7 +35,7 @@ from rhodecode.lib.caching_query import FromCache from rhodecode.model import BaseModel from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ - Notification + Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException @@ -46,16 +46,26 @@ from sqlalchemy.orm import joinedload log = logging.getLogger(__name__) -PERM_WEIGHTS = {'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 3} +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, +} class UserModel(BaseModel): def __get_user(self, user): - return self._get_instance(User, user) + return self._get_instance(User, user, callback=User.get_by_username) + + def __get_perm(self, permission): + return self._get_instance(Permission, permission, + callback=Permission.get_by_key) def get(self, user_id, cache=False): user = self.sa.query(User) @@ -348,9 +358,12 @@ class UserModel(BaseModel): :param user: user instance to fill his perms """ - - user.permissions['repositories'] = {} - user.permissions['global'] = set() + RK = 'repositories' + GK = 'repositories_groups' + GLOBAL = 'global' + user.permissions[RK] = {} + user.permissions[GK] = {} + user.permissions[GLOBAL] = set() #====================================================================== # fetch default permissions @@ -358,36 +371,45 @@ class UserModel(BaseModel): default_user = User.get_by_username('default', cache=True) default_user_id = default_user.user_id - default_perms = Permission.get_default_perms(default_user_id) + default_repo_perms = Permission.get_default_perms(default_user_id) + default_repo_groups_perms = Permission.get_default_group_perms(default_user_id) if user.is_admin: #================================================================== - # #admin have all default rights set to admin + # admin user have all default rights for repositories + # and groups set to admin #================================================================== - user.permissions['global'].add('hg.admin') + user.permissions[GLOBAL].add('hg.admin') - for perm in default_perms: + # repositories + for perm in default_repo_perms: + r_k = perm.UserRepoToPerm.repository.repo_name p = 'repository.admin' - user.permissions['repositories'][perm.UserRepoToPerm. - repository.repo_name] = p + user.permissions[RK][r_k] = p + + # repositories groups + for perm in default_repo_groups_perms: + rg_k = perm.UserRepoGroupToPerm.group.group_name + p = 'group.admin' + user.permissions[GK][rg_k] = p else: #================================================================== - # set default permissions + # set default permissions first for repositories and groups #================================================================== uid = user.user_id - # default global + # default global permissions 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) + user.permissions[GLOBAL].add(perm.permission.permission_name) # default for repositories - for perm in default_perms: - if perm.Repository.private and not (perm.Repository.user_id == - uid): + 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: @@ -396,8 +418,13 @@ class UserModel(BaseModel): else: p = perm.Permission.permission_name - user.permissions['repositories'][perm.UserRepoToPerm. - repository.repo_name] = p + user.permissions[RK][r_k] = p + + # default for repositories groups + 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 #================================================================== # overwrite default with user permissions if any @@ -409,25 +436,24 @@ class UserModel(BaseModel): .filter(UserToPerm.user_id == uid).all() for perm in user_perms: - user.permissions['global'].add(perm.permission.permission_name) + user.permissions[GLOBAL].add(perm.permission.permission_name) # user repositories - user_repo_perms = self.sa.query(UserRepoToPerm, Permission, - Repository)\ - .join((Repository, UserRepoToPerm.repository_id == - Repository.repo_id))\ - .join((Permission, UserRepoToPerm.permission_id == - Permission.permission_id))\ - .filter(UserRepoToPerm.user_id == uid).all() + user_repo_perms = \ + self.sa.query(UserRepoToPerm, Permission, Repository)\ + .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ + .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\ + .filter(UserRepoToPerm.user_id == uid)\ + .all() for perm in user_repo_perms: # set admin if owner + r_k = perm.UserRepoToPerm.repository.repo_name if perm.Repository.user_id == uid: p = 'repository.admin' else: p = perm.Permission.permission_name - user.permissions['repositories'][perm.UserRepoToPerm. - repository.repo_name] = p + user.permissions[RK][r_k] = p #================================================================== # check if user is part of groups for this repository and fill in @@ -442,30 +468,44 @@ class UserModel(BaseModel): .filter(UsersGroupMember.user_id == uid).all() for perm in user_perms_from_users_groups: - user.permissions['global'].add(perm.permission.permission_name) + user.permissions[GLOBAL].add(perm.permission.permission_name) # users group repositories - user_repo_perms_from_users_groups = self.sa.query( - UsersGroupRepoToPerm, - Permission, Repository,)\ - .join((Repository, UsersGroupRepoToPerm.repository_id == - Repository.repo_id))\ - .join((Permission, UsersGroupRepoToPerm.permission_id == - Permission.permission_id))\ - .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == - UsersGroupMember.users_group_id))\ - .filter(UsersGroupMember.user_id == uid).all() + user_repo_perms_from_users_groups = \ + self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\ + .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\ + .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\ + .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\ + .filter(UsersGroupMember.user_id == uid)\ + .all() for perm in user_repo_perms_from_users_groups: + r_k = perm.UsersGroupRepoToPerm.repository.repo_name p = perm.Permission.permission_name - cur_perm = user.permissions['repositories'][perm. - UsersGroupRepoToPerm. - repository.repo_name] + cur_perm = user.permissions[RK][r_k] # overwrite permission only if it's greater than permission # given from other sources if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]: - user.permissions['repositories'][perm.UsersGroupRepoToPerm. - repository.repo_name] = p + user.permissions[RK][r_k] = p + + #================================================================== + # get access for this user for repos group and override defaults + #================================================================== + + # user repositories groups + user_repo_groups_perms = \ + self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\ + .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ + .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\ + .filter(UserRepoToPerm.user_id == uid)\ + .all() + + 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 PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]: + user.permissions[GK][rg_k] = p return user @@ -480,23 +520,28 @@ class UserModel(BaseModel): .filter(UserToPerm.permission == perm).scalar() is not None def grant_perm(self, user, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class ' - 'got %s instead' % type(perm)) + """ + Grant user global permissions + :param user: + :param perm: + """ user = self.__get_user(user) - + perm = self.__get_perm(perm) new = UserToPerm() new.user = user new.permission = perm self.sa.add(new) def revoke_perm(self, user, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class ' - 'got %s instead' % type(perm)) + """ + Revoke users global permissions + :param user: + :param perm: + """ user = self.__get_user(user) + perm = self.__get_perm(perm) obj = UserToPerm.query().filter(UserToPerm.user == user)\ .filter(UserToPerm.permission == perm).scalar() diff --git a/rhodecode/model/users_group.py b/rhodecode/model/users_group.py index 738e6aaa..b07958b0 100644 --- a/rhodecode/model/users_group.py +++ b/rhodecode/model/users_group.py @@ -38,7 +38,12 @@ log = logging.getLogger(__name__) class UsersGroupModel(BaseModel): def __get_users_group(self, users_group): - return self._get_instance(UsersGroup, users_group) + return self._get_instance(UsersGroup, users_group, + callback=UsersGroup.get_by_group_name) + + def __get_perm(self, permission): + return self._get_instance(Permission, permission, + callback=Permission.get_by_key) def get(self, users_group_id, cache=False): return UsersGroup.get(users_group_id) @@ -80,7 +85,15 @@ class UsersGroupModel(BaseModel): log.error(traceback.format_exc()) raise - def delete(self, users_group): + def delete(self, users_group, force=False): + """ + Deletes repos 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_users_group(users_group) @@ -88,7 +101,7 @@ class UsersGroupModel(BaseModel): assigned_groups = UsersGroupRepoToPerm.query()\ .filter(UsersGroupRepoToPerm.users_group == users_group).all() - if assigned_groups: + if assigned_groups and force is False: raise UsersGroupsAssignedException('RepoGroup assigned to %s' % assigned_groups) @@ -118,10 +131,8 @@ class UsersGroupModel(BaseModel): raise def has_perm(self, users_group, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - users_group = self.__get_users_group(users_group) + perm = self.__get_perm(perm) return UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group == users_group)\ @@ -139,10 +150,8 @@ class UsersGroupModel(BaseModel): self.sa.add(new) def revoke_perm(self, users_group, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - users_group = self.__get_users_group(users_group) + perm = self.__get_perm(perm) obj = UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group == users_group)\ diff --git a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html new file mode 100644 index 00000000..533ce17b --- /dev/null +++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html @@ -0,0 +1,270 @@ +<table id="permissions_manage" class="noborder"> + <tr> + <td>${_('none')}</td> + <td>${_('read')}</td> + <td>${_('write')}</td> + <td>${_('admin')}</td> + <td>${_('member')}</td> + <td></td> + </tr> + ## USERS + %for r2p in c.repos_group.repo_group_to_perm: + <tr id="id${id(r2p.user.username)}"> + <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td> + <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td> + <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td> + <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td> + <td style="white-space: nowrap;"> + <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username} + </td> + <td> + %if r2p.user.username !='default': + <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')"> + ${_('revoke')} + </span> + %endif + </td> + </tr> + %endfor + + ## USERS GROUPS + %for g2p in c.repos_group.users_group_to_perm: + <tr id="id${id(g2p.users_group.users_group_name)}"> + <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td> + <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td> + <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td> + <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td> + <td style="white-space: nowrap;"> + <img style="vertical-align:bottom" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name} + </td> + <td> + <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')"> + ${_('revoke')} + </span> + </td> + </tr> + %endfor + <tr id="add_perm_input"> + <td>${h.radio('perm_new_member','group.none')}</td> + <td>${h.radio('perm_new_member','group.read')}</td> + <td>${h.radio('perm_new_member','group.write')}</td> + <td>${h.radio('perm_new_member','group.admin')}</td> + <td class='ac'> + <div class="perm_ac" id="perm_ac"> + ${h.text('perm_new_member_name',class_='yui-ac-input')} + ${h.hidden('perm_new_member_type')} + <div id="perm_container"></div> + </div> + </td> + <td></td> + </tr> + <tr> + <td colspan="6"> + <span id="add_perm" class="add_icon" style="cursor: pointer;"> + ${_('Add another member')} + </span> + </td> + </tr> +</table> +<script type="text/javascript"> +function ajaxActionUser(user_id, field_id) { + var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.name)}"; + var callback = { + success: function (o) { + var tr = YUD.get(String(field_id)); + tr.parentNode.removeChild(tr); + }, + failure: function (o) { + alert("${_('Failed to remove user')}"); + }, + }; + var postData = '_method=delete&user_id=' + user_id; + var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData); +}; + +function ajaxActionUsersGroup(users_group_id,field_id){ + var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.name)}"; + var callback = { + success:function(o){ + var tr = YUD.get(String(field_id)); + tr.parentNode.removeChild(tr); + }, + failure:function(o){ + alert("${_('Failed to remove users group')}"); + }, + }; + var postData = '_method=delete&users_group_id='+users_group_id; + var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData); +}; + +YUE.onDOMReady(function () { + if (!YUD.hasClass('perm_new_member_name', 'error')) { + YUD.setStyle('add_perm_input', 'display', 'none'); + } + YAHOO.util.Event.addListener('add_perm', 'click', function () { + YUD.setStyle('add_perm_input', 'display', ''); + YUD.setStyle('add_perm', 'opacity', '0.6'); + YUD.setStyle('add_perm', 'cursor', 'default'); + }); +}); + +YAHOO.example.FnMultipleFields = function () { + var myUsers = ${c.users_array|n}; + var myGroups = ${c.users_groups_array|n}; + + // Define a custom search function for the DataSource of users + var matchUsers = function (sQuery) { + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myUsers.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + contact = myUsers[i]; + if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) { + matches[matches.length] = contact; + } + } + return matches; + }; + + // Define a custom search function for the DataSource of usersGroups + var matchGroups = function (sQuery) { + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myGroups.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + matched_group = myGroups[i]; + if (matched_group.grname.toLowerCase().indexOf(query) > -1) { + matches[matches.length] = matched_group; + } + } + return matches; + }; + + //match all + var matchAll = function (sQuery) { + u = matchUsers(sQuery); + g = matchGroups(sQuery); + return u.concat(g); + }; + + // DataScheme for members + var memberDS = new YAHOO.util.FunctionDataSource(matchAll); + memberDS.responseSchema = { + fields: ["id", "fname", "lname", "nname", "grname", "grmembers"] + }; + + // DataScheme for owner + var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers); + ownerDS.responseSchema = { + fields: ["id", "fname", "lname", "nname"] + }; + + // Instantiate AutoComplete for perms + var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS); + membersAC.useShadow = false; + membersAC.resultTypeList = false; + + // Instantiate AutoComplete for owner + var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS); + ownerAC.useShadow = false; + ownerAC.resultTypeList = false; + + + // Helper highlight function for the formatter + var highlightMatch = function (full, snippet, matchindex) { + return full.substring(0, matchindex) + "<span class='match'>" + full.substr(matchindex, snippet.length) + "</span>" + full.substring(matchindex + snippet.length); + }; + + // Custom formatter to highlight the matching letters + var custom_formatter = function (oResultData, sQuery, sResultMatch) { + var query = sQuery.toLowerCase(); + + if (oResultData.grname != undefined) { + var grname = oResultData.grname; + var grmembers = oResultData.grmembers; + var grnameMatchIndex = grname.toLowerCase().indexOf(query); + var grprefix = "${_('Group')}: "; + var grsuffix = " (" + grmembers + " ${_('members')})"; + + if (grnameMatchIndex > -1) { + return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix; + } + + return grprefix + oResultData.grname + grsuffix; + } else if (oResultData.fname != undefined) { + + var fname = oResultData.fname, + lname = oResultData.lname, + nname = oResultData.nname || "", + // Guard against null value + fnameMatchIndex = fname.toLowerCase().indexOf(query), + lnameMatchIndex = lname.toLowerCase().indexOf(query), + nnameMatchIndex = nname.toLowerCase().indexOf(query), + displayfname, displaylname, displaynname; + + if (fnameMatchIndex > -1) { + displayfname = highlightMatch(fname, query, fnameMatchIndex); + } else { + displayfname = fname; + } + + if (lnameMatchIndex > -1) { + displaylname = highlightMatch(lname, query, lnameMatchIndex); + } else { + displaylname = lname; + } + + if (nnameMatchIndex > -1) { + displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")"; + } else { + displaynname = nname ? "(" + nname + ")" : ""; + } + + return displayfname + " " + displaylname + " " + displaynname; + } else { + return ''; + } + }; + membersAC.formatResult = custom_formatter; + ownerAC.formatResult = custom_formatter; + + var myHandler = function (sType, aArgs) { + + var myAC = aArgs[0]; // reference back to the AC instance + var elLI = aArgs[1]; // reference to the selected LI element + var oData = aArgs[2]; // object literal of selected item's result data + //fill the autocomplete with value + if (oData.nname != undefined) { + //users + myAC.getInputEl().value = oData.nname; + YUD.get('perm_new_member_type').value = 'user'; + } else { + //groups + myAC.getInputEl().value = oData.grname; + YUD.get('perm_new_member_type').value = 'users_group'; + } + + }; + + membersAC.itemSelectEvent.subscribe(myHandler); + if(ownerAC.itemSelectEvent){ + ownerAC.itemSelectEvent.subscribe(myHandler); + } + + return { + memberDS: memberDS, + ownerDS: ownerDS, + membersAC: membersAC, + ownerAC: ownerAC, + }; +}(); + +</script> diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html index 89bca5f7..b3681099 100644 --- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html @@ -53,9 +53,18 @@ ${h.select('group_parent_id','',c.repo_groups,class_="medium")} </div> </div> + <div class="field"> + <div class="label"> + <label for="input">${_('Permissions')}:</label> + </div> + <div class="input"> + <%include file="repos_group_edit_perms.html"/> + </div> + </div> <div class="buttons"> - ${h.submit('save',_('save'),class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-button")} + ${h.reset('reset',_('Reset'),class_="ui-button")} </div> </div> </div> diff --git a/rhodecode/templates/index_base.html b/rhodecode/templates/index_base.html index e5684dbb..0aaba7da 100644 --- a/rhodecode/templates/index_base.html +++ b/rhodecode/templates/index_base.html @@ -38,7 +38,9 @@ </div> </td> <td>${gr.group_description}</td> - ##<td><b>${gr.repositories.count()}</b></td> + ## this is commented out since for multi nested repos can be HEAVY! + ## in number of executed queries during traversing uncomment at will + ##<td><b>${gr.repositories_recursive_count}</b></td> </tr> % endfor diff --git a/rhodecode/tests/test_models.py b/rhodecode/tests/test_models.py index 434071cb..b0fd6e48 100644 --- a/rhodecode/tests/test_models.py +++ b/rhodecode/tests/test_models.py @@ -12,13 +12,27 @@ from rhodecode.model.user import UserModel from rhodecode.model.meta import Session from rhodecode.model.notification import NotificationModel from rhodecode.model.users_group import UsersGroupModel +from rhodecode.lib.auth import AuthUser + + +def _make_group(path, desc='desc', parent_id=None, + skip_if_exists=False): + + gr = RepoGroup.get_by_group_name(path) + if gr and skip_if_exists: + return gr + + gr = ReposGroupModel().create(path, desc, parent_id) + Session.commit() + return gr + class TestReposGroups(unittest.TestCase): def setUp(self): - self.g1 = self.__make_group('test1', skip_if_exists=True) - self.g2 = self.__make_group('test2', skip_if_exists=True) - self.g3 = self.__make_group('test3', skip_if_exists=True) + self.g1 = _make_group('test1', skip_if_exists=True) + self.g2 = _make_group('test2', skip_if_exists=True) + self.g3 = _make_group('test3', skip_if_exists=True) def tearDown(self): print 'out' @@ -31,102 +45,81 @@ class TestReposGroups(unittest.TestCase): def _check_folders(self): print os.listdir(TESTS_TMP_PATH) - def __make_group(self, path, desc='desc', parent_id=None, - skip_if_exists=False): - - gr = RepoGroup.get_by_group_name(path) - if gr and skip_if_exists: - return gr - - form_data = dict(group_name=path, - group_description=desc, - group_parent_id=parent_id) - gr = ReposGroupModel().create(form_data) - Session.commit() - return gr - def __delete_group(self, id_): ReposGroupModel().delete(id_) - def __update_group(self, id_, path, desc='desc', parent_id=None): form_data = dict(group_name=path, group_description=desc, - group_parent_id=parent_id) + group_parent_id=parent_id, + perms_updates=[], + perms_new=[]) gr = ReposGroupModel().update(id_, form_data) return gr def test_create_group(self): - g = self.__make_group('newGroup') + g = _make_group('newGroup') self.assertEqual(g.full_path, 'newGroup') self.assertTrue(self.__check_path('newGroup')) - def test_create_same_name_group(self): - self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup')) + self.assertRaises(IntegrityError, lambda:_make_group('newGroup')) Session.rollback() def test_same_subgroup(self): - sg1 = self.__make_group('sub1', parent_id=self.g1.group_id) + sg1 = _make_group('sub1', parent_id=self.g1.group_id) self.assertEqual(sg1.parent_group, self.g1) self.assertEqual(sg1.full_path, 'test1/sub1') self.assertTrue(self.__check_path('test1', 'sub1')) - ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id) + ssg1 = _make_group('subsub1', parent_id=sg1.group_id) self.assertEqual(ssg1.parent_group, sg1) self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1') self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1')) - def test_remove_group(self): - sg1 = self.__make_group('deleteme') + sg1 = _make_group('deleteme') self.__delete_group(sg1.group_id) self.assertEqual(RepoGroup.get(sg1.group_id), None) self.assertFalse(self.__check_path('deteteme')) - sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id) + sg1 = _make_group('deleteme', parent_id=self.g1.group_id) self.__delete_group(sg1.group_id) self.assertEqual(RepoGroup.get(sg1.group_id), None) self.assertFalse(self.__check_path('test1', 'deteteme')) - def test_rename_single_group(self): - sg1 = self.__make_group('initial') + sg1 = _make_group('initial') new_sg1 = self.__update_group(sg1.group_id, 'after') self.assertTrue(self.__check_path('after')) self.assertEqual(RepoGroup.get_by_group_name('initial'), None) - def test_update_group_parent(self): - sg1 = self.__make_group('initial', parent_id=self.g1.group_id) + sg1 = _make_group('initial', parent_id=self.g1.group_id) new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id) self.assertTrue(self.__check_path('test1', 'after')) self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None) - new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id) self.assertTrue(self.__check_path('test3', 'after')) self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None) - new_sg1 = self.__update_group(sg1.group_id, 'hello') self.assertTrue(self.__check_path('hello')) self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1) - - def test_subgrouping_with_repo(self): - g1 = self.__make_group('g1') - g2 = self.__make_group('g2') + g1 = _make_group('g1') + g2 = _make_group('g2') # create new repo form_data = dict(repo_name='john', @@ -150,13 +143,13 @@ class TestReposGroups(unittest.TestCase): RepoModel().update(r.repo_name, form_data) self.assertEqual(r.repo_name, 'g1/john') - self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id) self.assertTrue(self.__check_path('g2', 'g1')) # test repo self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name)) + class TestUser(unittest.TestCase): def __init__(self, methodName='runTest'): Session.remove() @@ -245,7 +238,6 @@ class TestNotifications(unittest.TestCase): self.assertEqual(len(unotification), len(usrs)) self.assertEqual([x.user.user_id for x in unotification], usrs) - def test_user_notifications(self): self.assertEqual([], Notification.query().all()) self.assertEqual([], UserNotification.query().all()) @@ -284,7 +276,6 @@ class TestNotifications(unittest.TestCase): == notification).all() self.assertEqual(un, []) - def test_delete_association(self): self.assertEqual([], Notification.query().all()) @@ -361,6 +352,7 @@ class TestNotifications(unittest.TestCase): self.assertEqual(NotificationModel() .get_unread_cnt_for_user(self.u3), 2) + class TestUsers(unittest.TestCase): def __init__(self, methodName='runTest'): @@ -401,4 +393,163 @@ class TestUsers(unittest.TestCase): #revoke UserModel().revoke_perm(self.u1, perm) Session.commit() - self.assertEqual(UserModel().has_perm(self.u1, perm),False) + self.assertEqual(UserModel().has_perm(self.u1, perm), False) + + +class TestPermissions(unittest.TestCase): + def __init__(self, methodName='runTest'): + super(TestPermissions, self).__init__(methodName=methodName) + + def setUp(self): + self.u1 = UserModel().create_or_update( + username=u'u1', password=u'qweqwe', + email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1' + ) + self.a1 = UserModel().create_or_update( + username=u'a1', password=u'qweqwe', + email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True + ) + Session.commit() + + def tearDown(self): + UserModel().delete(self.u1) + UserModel().delete(self.a1) + if hasattr(self, 'g1'): + ReposGroupModel().delete(self.g1.group_id) + if hasattr(self, 'g2'): + ReposGroupModel().delete(self.g2.group_id) + + if hasattr(self, 'ug1'): + UsersGroupModel().delete(self.ug1, force=True) + + Session.commit() + + def test_default_perms_set(self): + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.create.repository', u'repository.read', + u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.read'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + new_perm = 'repository.write' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm) + Session.commit() + + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], new_perm) + + def test_default_admin_perms_set(self): + a1_auth = AuthUser(user_id=self.a1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.admin']), + 'repositories': {u'vcs_test_hg': u'repository.admin'} + } + self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + new_perm = 'repository.write' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, perm=new_perm) + Session.commit() + # cannot really downgrade admins permissions !? they still get's set as + # admin ! + u1_auth = AuthUser(user_id=self.a1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + + def test_default_group_perms(self): + self.g1 = _make_group('test1', skip_if_exists=True) + self.g2 = _make_group('test2', skip_if_exists=True) + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'}, + 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.read'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + self.assertEqual(u1_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_default_admin_group_perms(self): + self.g1 = _make_group('test1', skip_if_exists=True) + self.g2 = _make_group('test2', skip_if_exists=True) + a1_auth = AuthUser(user_id=self.a1.user_id) + perms = { + 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'}, + 'global': set(['hg.admin']), + 'repositories': {u'vcs_test_hg': 'repository.admin'} + } + + self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + self.assertEqual(a1_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_propagated_permission_from_users_group(self): + # make group + self.ug1 = UsersGroupModel().create('G1') + # add user to group + UsersGroupModel().add_user_to_group(self.ug1, self.u1) + + # set permission to lower + new_perm = 'repository.none' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm) + Session.commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm) + + # grant perm for group this should override permission from user + new_perm = 'repository.write' + RepoModel().grant_users_group_permission(repo=HG_REPO, + group_name=self.ug1, + perm=new_perm) + # check perms + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.create.repository', u'repository.read', + u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.read'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm) + self.assertEqual(u1_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_propagated_permission_from_users_group_lower_weight(self): + # make group + self.ug1 = UsersGroupModel().create('G1') + # add user to group + UsersGroupModel().add_user_to_group(self.ug1, self.u1) + + # set permission to lower + new_perm_h = 'repository.write' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, + perm=new_perm_h) + Session.commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm_h) + + # grant perm for group this should NOT override permission from user + # since it's lower than granted + new_perm_l = 'repository.read' + RepoModel().grant_users_group_permission(repo=HG_REPO, + group_name=self.ug1, + perm=new_perm_l) + # check perms + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.create.repository', u'repository.read', + u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.write'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm_h) + self.assertEqual(u1_auth.permissions['repositories_groups'], + perms['repositories_groups']) |