diff --git a/README.rst b/README.rst --- a/README.rst +++ b/README.rst @@ -3,9 +3,9 @@ RhodeCode documentation! ======================== ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_ -with a built in push/pull server and full text search. +with a built in push/pull server and full text search and code-review. It works on http/https and has a built in permission/authentication system with -the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports +the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides simple API so it's easy integrable with existing external systems. RhodeCode is similar in some respects to github or bitbucket_, diff --git a/docs/api/api.rst b/docs/api/api.rst --- a/docs/api/api.rst +++ b/docs/api/api.rst @@ -91,6 +91,7 @@ Get's an user by username, Returns empty This command can be executed only using api_key belonging to user with admin rights. + INPUT:: api_key : "" @@ -122,6 +123,7 @@ get_users Lists all existing users. This command can be executed only using api_key belonging to user with admin rights. + INPUT:: api_key : "" @@ -145,12 +147,14 @@ OUTPUT:: ] error: null + create_user ----------- Creates new user or updates current one if such user exists. This command can be executed only using api_key belonging to user with admin rights. + INPUT:: api_key : "" @@ -174,12 +178,14 @@ OUTPUT:: } error: null + get_users_group --------------- Gets an existing users group. This command can be executed only using api_key belonging to user with admin rights. + INPUT:: api_key : "" @@ -210,12 +216,14 @@ OUTPUT:: } error : null + get_users_groups ---------------- Lists all existing users groups. This command can be executed only using api_key belonging to user with admin rights. + INPUT:: api_key : "" @@ -253,6 +261,7 @@ create_users_group Creates new users group. This command can be executed only using api_key belonging to user with admin rights + INPUT:: api_key : "" @@ -270,12 +279,14 @@ OUTPUT:: } error: null + add_user_to_users_group ----------------------- Adds a user to a users group. This command can be executed only using api_key belonging to user with admin rights + INPUT:: api_key : "" @@ -293,12 +304,14 @@ OUTPUT:: } error: null + get_repo -------- Gets an existing repository. This command can be executed only using api_key belonging to user with admin rights + INPUT:: api_key : "" @@ -338,12 +351,14 @@ OUTPUT:: } error: null + get_repos --------- Lists all existing repositories. This command can be executed only using api_key belonging to user with admin rights + INPUT:: api_key : "" @@ -372,6 +387,7 @@ at given revision. It's possible to spec `dirs`. This command can be executed only using api_key belonging to user with admin rights + INPUT:: api_key : "" @@ -395,7 +411,6 @@ OUTPUT:: error: null - create_repo ----------- @@ -405,6 +420,7 @@ If repository name contains "/", all nee For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent), and create "baz" repository with "bar" as group. + INPUT:: api_key : "" @@ -420,54 +436,106 @@ INPUT:: OUTPUT:: result: { - "id": "", - "msg": "Created new repository ", + "id": "", + "msg": "Created new repository ", } error: null -add_user_to_repo ----------------- + +grant_user_permission +--------------------- -Add a user to a repository. This command can be executed only using api_key -belonging to user with admin rights. -If "perm" is None, user will be removed from the repository. +Grant permission for user on given repository, or update existing one +if found. This command can be executed only using api_key belonging to user +with admin rights. + INPUT:: api_key : "" - method : "add_user_to_repo" + method : "grant_user_permission" args: { "repo_name" : "", "username" : "", - "perm" : "(None|repository.(read|write|admin))", + "perm" : "(repository.(none|read|write|admin))", + } + +OUTPUT:: + + result: { + "msg" : "Granted perm: for user: in repo: " + } + error: null + + +revoke_user_permission +---------------------- + +Revoke permission for user on given repository. This command can be executed +only using api_key belonging to user with admin rights. + + +INPUT:: + + api_key : "" + method : "revoke_user_permission" + args: { + "repo_name" : "", + "username" : "", } OUTPUT:: result: { - "msg" : "Added perm: for in repo: " + "msg" : "Revoked perm for user: in repo: " } error: null -add_users_group_to_repo ------------------------ + +grant_users_group_permission +---------------------------- -Add a users group to a repository. This command can be executed only using -api_key belonging to user with admin rights. If "perm" is None, group will -be removed from the repository. +Grant permission for users group on given repository, or update +existing one if found. This command can be executed only using +api_key belonging to user with admin rights. + INPUT:: api_key : "" - method : "add_users_group_to_repo" + method : "grant_users_group_permission" + args: { + "repo_name" : "", + "group_name" : "", + "perm" : "(repository.(none|read|write|admin))", + } + +OUTPUT:: + + result: { + "msg" : "Granted perm: for group: in repo: " + } + error: null + + +revoke_users_group_permission +----------------------------- + +Revoke permission for users group on given repository.This command can be +executed only using api_key belonging to user with admin rights. + +INPUT:: + + api_key : "" + method : "revoke_users_group_permission" args: { "repo_name" : "", - "group_name" : "", - "perm" : "(None|repository.(read|write|admin))", + "users_group" : "", } + OUTPUT:: - + result: { - "msg" : Added perm: for in repo: " + "msg" : "Revoked perm for group: in repo: " } - + error: null \ No newline at end of file diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -51,8 +51,8 @@ def make_app(global_conf, full_stack=Tru from rhodecode.lib.profiler import ProfilingMiddleware app = ProfilingMiddleware(app) + if asbool(full_stack): - 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=Tru 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 --- 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 --- 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 --- 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(BaseControll 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(BaseControll 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(BaseControll 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(BaseControll 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(BaseControll 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(BaseControll 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 --- 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) + 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): + """ + Revoke permission for user on given repository + + :param repo_name: + :param username: """ - Add permission for a users group to a repository + + 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) - :param apiuser: + 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 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 --- a/rhodecode/controllers/home.py +++ b/rhodecode/controllers/home.py @@ -30,7 +30,7 @@ from paste.httpexceptions import HTTPBad 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 --- 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='ut 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 --- 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(Perm 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 --- 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'), + # 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'), - ('hg.register.auto_activate', 'Register new user with ' - 'RhodeCode without auto ' - 'activation'), - ] + ('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 --- a/rhodecode/lib/hooks.py +++ b/rhodecode/lib/hooks.py @@ -130,7 +130,7 @@ def log_create_repository(repository_dic 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_dic """ - return 0 \ No newline at end of file + return 0 diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- 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_nam 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, re 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, re 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 --- 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 --- 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 --- 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' - 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') + 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)) + 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 + 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() + # 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 + 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= 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= 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={}, su #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_dat 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 --- 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 --- 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 + + :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 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 + 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 --- 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 --- 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 --- 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 --- a/rhodecode/model/user.py +++ b/rhodecode/model/user.py @@ -35,7 +35,7 @@ from rhodecode.lib.caching_query import 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 --- 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 --- /dev/null +++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html @@ -0,0 +1,270 @@ + + + + + + + + + + ## USERS + %for r2p in c.repos_group.repo_group_to_perm: + + + + + + + + + %endfor + + ## USERS GROUPS + %for g2p in c.repos_group.users_group_to_perm: + + + + + + + + + %endfor + + + + + + + + + + + +
${_('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': + + ${_('revoke')} + + %endif +
${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')} + +
${h.radio('perm_new_member','group.none')}${h.radio('perm_new_member','group.read')}${h.radio('perm_new_member','group.write')}${h.radio('perm_new_member','group.admin')} +
+ ${h.text('perm_new_member_name',class_='yui-ac-input')} + ${h.hidden('perm_new_member_type')} +
+
+
+ + ${_('Add another member')} + +
+ diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html --- 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")} +
+
+ +
+
+ <%include file="repos_group_edit_perms.html"/> +
+
- ${h.submit('save',_('save'),class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-button")} + ${h.reset('reset',_('Reset'),class_="ui-button")}
diff --git a/rhodecode/templates/index_base.html b/rhodecode/templates/index_base.html --- a/rhodecode/templates/index_base.html +++ b/rhodecode/templates/index_base.html @@ -38,7 +38,9 @@ ${gr.group_description} - ##${gr.repositories.count()} + ## this is commented out since for multi nested repos can be HEAVY! + ## in number of executed queries during traversing uncomment at will + ##${gr.repositories_recursive_count} % endfor diff --git a/rhodecode/tests/test_models.py b/rhodecode/tests/test_models.py --- a/rhodecode/tests/test_models.py +++ b/rhodecode/tests/test_models.py @@ -12,13 +12,27 @@ from rhodecode.model.user import UserMod 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.TestCas 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.TestCas == notification).all() self.assertEqual(un, []) - def test_delete_association(self): self.assertEqual([], Notification.query().all()) @@ -361,6 +352,7 @@ class TestNotifications(unittest.TestCas 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']) diff --git a/test.ini b/test.ini --- a/test.ini +++ b/test.ini @@ -89,7 +89,7 @@ beaker.cache.lock_dir=/tmp/data/cache/lo beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long beaker.cache.super_short_term.type=memory -beaker.cache.super_short_term.expire=10 +beaker.cache.super_short_term.expire=1 beaker.cache.super_short_term.key_length = 256 beaker.cache.short_term.type=memory @@ -101,7 +101,7 @@ beaker.cache.long_term.expire=36000 beaker.cache.long_term.key_length = 256 beaker.cache.sql_cache_short.type=memory -beaker.cache.sql_cache_short.expire=10 +beaker.cache.sql_cache_short.expire=1 beaker.cache.sql_cache_short.key_length = 256 beaker.cache.sql_cache_med.type=memory