changeset 1982:87f0800abc7b beta

#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
author Marcin Kuzminski <marcin@python-works.com>
date Sat, 28 Jan 2012 01:06:29 +0200
parents 518f87919375
children 8fa8d0d034e6
files README.rst docs/api/api.rst rhodecode/config/middleware.py rhodecode/config/routing.py rhodecode/controllers/admin/repos.py rhodecode/controllers/admin/repos_groups.py rhodecode/controllers/api/api.py rhodecode/controllers/home.py rhodecode/lib/__init__.py rhodecode/lib/auth.py rhodecode/lib/db_manage.py rhodecode/lib/hooks.py rhodecode/lib/utils.py rhodecode/model/__init__.py rhodecode/model/db.py rhodecode/model/forms.py rhodecode/model/notification.py rhodecode/model/repo.py rhodecode/model/repo_permission.py rhodecode/model/repos_group.py rhodecode/model/scm.py rhodecode/model/user.py rhodecode/model/users_group.py rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html rhodecode/templates/admin/repos_groups/repos_groups_edit.html rhodecode/templates/index_base.html rhodecode/tests/test_models.py test.ini
diffstat 28 files changed, 1463 insertions(+), 394 deletions(-) [+]
line wrap: on
line diff
--- a/README.rst	Sun Feb 05 21:45:15 2012 +0200
+++ b/README.rst	Sat Jan 28 01:06:29 2012 +0200
@@ -3,9 +3,9 @@
 ========================
 
 ``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_, 
--- a/docs/api/api.rst	Sun Feb 05 21:45:15 2012 +0200
+++ b/docs/api/api.rst	Sat Jan 28 01:06:29 2012 +0200
@@ -91,6 +91,7 @@
 This command can be executed only using api_key belonging to user with admin 
 rights.
 
+
 INPUT::
 
     api_key : "<api_key>"
@@ -122,6 +123,7 @@
 Lists all existing users. This command can be executed only using api_key
 belonging to user with admin rights.
 
+
 INPUT::
 
     api_key : "<api_key>"
@@ -145,12 +147,14 @@
             ]
     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 : "<api_key>"
@@ -174,12 +178,14 @@
             }
     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 : "<api_key>"
@@ -210,12 +216,14 @@
              }
     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 : "<api_key>"
@@ -253,6 +261,7 @@
 Creates new users group. This command can be executed only using api_key
 belonging to user with admin rights
 
+
 INPUT::
 
     api_key : "<api_key>"
@@ -270,12 +279,14 @@
             }
     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 : "<api_key>"
@@ -293,12 +304,14 @@
             }
     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 : "<api_key>"
@@ -338,12 +351,14 @@
             }
     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 : "<api_key>"
@@ -372,6 +387,7 @@
 `dirs`. This command can be executed only using api_key belonging to user 
 with admin rights
 
+
 INPUT::
 
     api_key : "<api_key>"
@@ -395,7 +411,6 @@
     error:  null
 
 
-
 create_repo
 -----------
 
@@ -405,6 +420,7 @@
 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 : "<api_key>"
@@ -420,54 +436,106 @@
 OUTPUT::
 
     result: {
-                "id": "<newrepoid>",
-                "msg": "Created new repository <reponame>",
+              "id": "<newrepoid>",
+              "msg": "Created new repository <reponame>",
             }
     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 : "<api_key>"
-    method :  "add_user_to_repo"
+    method :  "grant_user_permission"
     args:     {
                 "repo_name" :  "<reponame>",
                 "username" :   "<username>",
-                "perm" :       "(None|repository.(read|write|admin))",
+                "perm" :       "(repository.(none|read|write|admin))",
+              }
+
+OUTPUT::
+
+    result: {
+              "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
+            }
+    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 : "<api_key>"
+    method  : "revoke_user_permission"
+    args:     {
+                "repo_name" :  "<reponame>",
+                "username" :   "<username>",
               }
 
 OUTPUT::
 
     result: {
-                "msg" : "Added perm: <perm> for <username> in repo: <reponame>"
+              "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
             }
     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 : "<api_key>"
-    method :  "add_users_group_to_repo"
+    method :  "grant_users_group_permission"
+    args:     {
+                "repo_name" : "<reponame>",
+                "group_name" : "<usersgroupname>",
+                "perm" : "(repository.(none|read|write|admin))",
+              }
+
+OUTPUT::
+
+    result: {
+              "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
+            }
+    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 : "<api_key>"
+    method  : "revoke_users_group_permission"
     args:     {
                 "repo_name" :  "<reponame>",
-                "group_name" : "<groupname>",
-                "perm" :       "(None|repository.(read|write|admin))",
+                "users_group" :   "<usersgroupname>",
               }
+
 OUTPUT::
-    
+
     result: {
-                "msg" : Added perm: <perm> for <groupname> in repo: <reponame>"
+              "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
             }
-
+    error:  null
\ No newline at end of file
--- a/rhodecode/config/middleware.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/config/middleware.py	Sat Jan 28 01:06:29 2012 +0200
@@ -51,8 +51,8 @@
         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 @@
         app = Cascade([static_app, app])
         app = make_gzip_middleware(app, global_conf, compress_level=1)
 
-
     app.config = config
 
     return app
--- a/rhodecode/config/routing.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/config/routing.py	Sat Jan 28 01:06:29 2012 +0200
@@ -113,8 +113,9 @@
                                             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 @@
         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 @@
         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 @@
         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 @@
                   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 @@
                 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 @@
                  controller='followers', action='followers',
                  conditions=dict(function=check_repo))
 
-
     return rmap
--- a/rhodecode/controllers/admin/repos.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/controllers/admin/repos.py	Sat Jan 28 01:06:29 2012 +0200
@@ -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 @@
 
         return redirect(url('repos'))
 
-
     @HasRepoPermissionAllDecorator('repository.admin')
     def delete_perm_user(self, repo_name):
         """
@@ -287,10 +286,11 @@
         """
 
         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 @@
 
         :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 @@
         """
 
         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'),
--- a/rhodecode/controllers/admin/repos_groups.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/controllers/admin/repos_groups.py	Sat Jan 28 01:06:29 2012 +0200
@@ -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 @@
 
 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 @@
         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 @@
 
         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 @@
                                           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 @@
         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 @@
 
         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 @@
         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'),
--- a/rhodecode/controllers/api/api.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/controllers/api/api.py	Sat Jan 28 01:06:29 2012 +0200
@@ -401,13 +401,7 @@
             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 @@
             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 @@
             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 @@
             )
 
     @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 @@
             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
+                )
+            )
--- a/rhodecode/controllers/home.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/controllers/home.py	Sat Jan 28 01:06:29 2012 +0200
@@ -30,7 +30,7 @@
 
 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 @@
         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')
 
--- a/rhodecode/lib/__init__.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/lib/__init__.py	Sat Jan 28 01:06:29 2012 +0200
@@ -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 @@
     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 @@
     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 @@
 
     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 @@
                    "was: %s" % err)
         return None
 
+
 def extract_mentioned_users(s):
     """
     Returns unique usernames from given string s that have @mention
--- a/rhodecode/lib/auth.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/lib/auth.py	Sat Jan 28 01:06:29 2012 +0200
@@ -31,7 +31,7 @@
 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 @@
 
 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 @@
     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 @@
         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 @@
             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 @@
             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 @@
         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
 #==============================================================================
--- a/rhodecode/lib/db_manage.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/lib/db_manage.py	Sat Jan 28 01:06:29 2012 +0200
@@ -442,23 +442,28 @@
 
     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()
--- a/rhodecode/lib/hooks.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/lib/hooks.py	Sat Jan 28 01:06:29 2012 +0200
@@ -130,7 +130,7 @@
     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 @@
     """
 
 
-    return 0
\ No newline at end of file
+    return 0
--- a/rhodecode/lib/utils.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/lib/utils.py	Sat Jan 28 01:06:29 2012 +0200
@@ -52,6 +52,7 @@
 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 @@
     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 @@
     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 @@
 
     return False
 
+
 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
     while True:
         ok = raw_input(prompt)
@@ -317,7 +324,8 @@
     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 @@
 
     # 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 @@
             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 @@
 
     return added, removed
 
+
 # set cache regions for beaker so celery can utilise it
 def add_cache(settings):
     cache_settings = {'regions': None}
--- a/rhodecode/model/__init__.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/__init__.py	Sat Jan 28 01:06:29 2012 +0200
@@ -74,12 +74,13 @@
         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 @@
             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)
--- a/rhodecode/model/db.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/db.py	Sat Jan 28 01:06:29 2012 +0200
@@ -717,6 +717,9 @@
     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 @@
     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 @@
     @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()
 
--- a/rhodecode/model/forms.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/forms.py	Sat Jan 28 01:06:29 2012 +0200
@@ -388,57 +388,66 @@
     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 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 @@
                                         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 @@
             #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 @@
         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
 
--- a/rhodecode/model/notification.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/notification.py	Sat Jan 28 01:06:29 2012 +0200
@@ -42,10 +42,7 @@
 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):
--- a/rhodecode/model/repo.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/repo.py	Sat Jan 28 01:06:29 2012 +0200
@@ -28,9 +28,9 @@
 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.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 @@
             # 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 @@
             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 @@
         """
         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 @@
             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 @@
 
         backend(repo_path, create=True, src_url=clone_uri)
 
-
     def __rename_repo(self, old, new):
         """
         renames repository on filesystem
@@ -406,8 +465,9 @@
         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 @@
         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))
--- a/rhodecode/model/repo_permission.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/repo_permission.py	Sat Jan 28 01:06:29 2012 +0200
@@ -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) \
--- a/rhodecode/model/repos_group.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/repos_group.py	Sat Jan 28 01:06:29 2012 +0200
@@ -28,18 +28,32 @@
 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 @@
         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 @@
             # 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 @@
 
         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 @@
         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)
--- a/rhodecode/model/scm.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/scm.py	Sat Jan 28 01:06:29 2012 +0200
@@ -36,12 +36,12 @@
 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 @@
         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 @@
             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 @@
 
         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
--- a/rhodecode/model/user.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/user.py	Sat Jan 28 01:06:29 2012 +0200
@@ -35,7 +35,7 @@
 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 @@
 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 @@
 
         :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 @@
         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 @@
                 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 @@
                     .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 @@
                 .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 @@
             .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()
--- a/rhodecode/model/users_group.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/model/users_group.py	Sat Jan 28 01:06:29 2012 +0200
@@ -38,7 +38,12 @@
 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 @@
             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 @@
             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 @@
             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 @@
         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)\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html	Sat Jan 28 01:06:29 2012 +0200
@@ -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>
--- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html	Sat Jan 28 01:06:29 2012 +0200
@@ -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>
--- a/rhodecode/templates/index_base.html	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/templates/index_base.html	Sat Jan 28 01:06:29 2012 +0200
@@ -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
 
--- a/rhodecode/tests/test_models.py	Sun Feb 05 21:45:15 2012 +0200
+++ b/rhodecode/tests/test_models.py	Sat Jan 28 01:06:29 2012 +0200
@@ -12,13 +12,27 @@
 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 @@
     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 @@
         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 @@
         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 @@
                                              == notification).all()
         self.assertEqual(un, [])
 
-
     def test_delete_association(self):
 
         self.assertEqual([], Notification.query().all())
@@ -361,6 +352,7 @@
         self.assertEqual(NotificationModel()
                          .get_unread_cnt_for_user(self.u3), 2)
 
+
 class TestUsers(unittest.TestCase):
 
     def __init__(self, methodName='runTest'):
@@ -401,4 +393,163 @@
         #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'])
--- a/test.ini	Sun Feb 05 21:45:15 2012 +0200
+++ b/test.ini	Sat Jan 28 01:06:29 2012 +0200
@@ -89,7 +89,7 @@
 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.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