diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -135,8 +135,35 @@ def make_map(config): action="update_perm", conditions=dict(method=["PUT"])) #ADMIN USERS REST ROUTES - rmap.resource('users_group', 'users_groups', - controller='admin/users_groups', path_prefix='/_admin') + with rmap.submapper(path_prefix='/_admin', + controller='admin/users_groups') as m: + m.connect("users_groups", "/users_groups", + action="create", conditions=dict(method=["POST"])) + m.connect("users_groups", "/users_groups", + action="index", conditions=dict(method=["GET"])) + m.connect("formatted_users_groups", "/users_groups.{format}", + action="index", conditions=dict(method=["GET"])) + m.connect("new_users_group", "/users_groups/new", + action="new", conditions=dict(method=["GET"])) + m.connect("formatted_new_users_group", "/users_groups/new.{format}", + action="new", conditions=dict(method=["GET"])) + m.connect("update_users_group", "/users_groups/{id}", + action="update", conditions=dict(method=["PUT"])) + m.connect("delete_users_group", "/users_groups/{id}", + action="delete", conditions=dict(method=["DELETE"])) + m.connect("edit_users_group", "/users_groups/{id}/edit", + action="edit", conditions=dict(method=["GET"])) + m.connect("formatted_edit_users_group", + "/users_groups/{id}.{format}/edit", + action="edit", conditions=dict(method=["GET"])) + m.connect("users_group", "/users_groups/{id}", + action="show", conditions=dict(method=["GET"])) + m.connect("formatted_users_group", "/users_groups/{id}.{format}", + action="show", conditions=dict(method=["GET"])) + + #EXTRAS USER ROUTES + m.connect("users_group_perm", "/users_groups_perm/{id}", + action="update_perm", conditions=dict(method=["PUT"])) #ADMIN GROUP REST ROUTES rmap.resource('group', 'groups', diff --git a/rhodecode/controllers/admin/users_groups.py b/rhodecode/controllers/admin/users_groups.py --- a/rhodecode/controllers/admin/users_groups.py +++ b/rhodecode/controllers/admin/users_groups.py @@ -36,9 +36,8 @@ from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import User, UsersGroup +from rhodecode.model.db import User, UsersGroup, Permission, UsersGroupToPerm from rhodecode.model.forms import UserForm, UsersGroupForm -from rhodecode.model.user import UserModel from rhodecode.model.users_group import UsersGroupModel log = logging.getLogger(__name__) @@ -123,10 +122,16 @@ class UsersGroupsController(BaseControll category='success') #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa) except formencode.Invalid, errors: + e = errors.error_dict or {} + + perm = Permission.get_by_key('hg.create.repository') + e.update({'create_repo_perm': + UsersGroupToPerm.has_perm(id, perm)}) + return htmlfill.render( render('admin/users_groups/users_group_edit.html'), defaults=errors.value, - errors=errors.error_dict or {}, + errors=e, prefix_error=False, encoding="UTF-8") except Exception: @@ -171,10 +176,38 @@ class UsersGroupsController(BaseControll c.available_members = [(x.user_id, x.username) for x in self.sa.query(User).all()] defaults = c.users_group.get_dict() - + perm = Permission.get_by_key('hg.create.repository') + defaults.update({'create_repo_perm': + UsersGroupToPerm.has_perm(id, perm)}) return htmlfill.render( render('admin/users_groups/users_group_edit.html'), defaults=defaults, encoding="UTF-8", force_defaults=False ) + + def update_perm(self, id): + """PUT /users_perm/id: Update an existing item""" + # url('users_group_perm', id=ID, method='put') + + grant_perm = request.POST.get('create_repo_perm', False) + + if grant_perm: + perm = Permission.get_by_key('hg.create.none') + UsersGroupToPerm.revoke_perm(id, perm) + + perm = Permission.get_by_key('hg.create.repository') + UsersGroupToPerm.grant_perm(id, perm) + h.flash(_("Granted 'repository create' permission to user"), + category='success') + + else: + perm = Permission.get_by_key('hg.create.repository') + UsersGroupToPerm.revoke_perm(id, perm) + + perm = Permission.get_by_key('hg.create.none') + UsersGroupToPerm.grant_perm(id, perm) + h.flash(_("Revoked 'repository create' permission to user"), + category='success') + + return redirect(url('edit_users_group', id=id)) diff --git a/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py b/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py --- a/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py +++ b/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py @@ -43,12 +43,17 @@ def upgrade(migrate_engine): UsersGroupMember().__table__.create() #========================================================================== + # Add table `users_group_repo_to_perm` + #========================================================================== + from rhodecode.model.db import UsersGroupRepoToPerm + UsersGroupRepoToPerm().__table__.create() + + #========================================================================== # Add table `users_group_to_perm` #========================================================================== from rhodecode.model.db import UsersGroupToPerm UsersGroupToPerm().__table__.create() - #========================================================================== # Upgrade of `users` table #========================================================================== diff --git a/rhodecode/model/__init__.py b/rhodecode/model/__init__.py --- a/rhodecode/model/__init__.py +++ b/rhodecode/model/__init__.py @@ -47,16 +47,19 @@ from rhodecode.model import meta log = logging.getLogger(__name__) + def init_model(engine): - """Initializes db session, bind the engine with the metadata, - Call this before using any of the tables or classes in the model, preferably - once in application start + """ + Initializes db session, bind the engine with the metadata, + Call this before using any of the tables or classes in the model, + preferably once in application start :param engine: engine to bind to """ log.info("initializing db for %s", engine) meta.Base.metadata.bind = engine + class BaseModel(object): """Base Model for all RhodeCode models, it adds sql alchemy session into instance of model diff --git a/rhodecode/model/caching_query.py b/rhodecode/model/caching_query.py --- a/rhodecode/model/caching_query.py +++ b/rhodecode/model/caching_query.py @@ -18,11 +18,13 @@ The rest of what's here are standard SQL Beaker constructs. """ +import beaker from beaker.exceptions import BeakerException + from sqlalchemy.orm.interfaces import MapperOption from sqlalchemy.orm.query import Query from sqlalchemy.sql import visitors -import beaker + class CachingQuery(Query): """A Query subclass which optionally loads full results from a Beaker @@ -74,7 +76,8 @@ class CachingQuery(Query): """ if hasattr(self, '_cache_parameters'): - return self.get_value(createfunc=lambda: list(Query.__iter__(self))) + return self.get_value(createfunc=lambda: + list(Query.__iter__(self))) else: return Query.__iter__(self) @@ -103,11 +106,13 @@ class CachingQuery(Query): cache, cache_key = _get_cache_parameters(self) cache.put(cache_key, value) + def query_callable(manager): def query(*arg, **kw): return CachingQuery(manager, *arg, **kw) return query + def get_cache_region(name, region): if region not in beaker.cache.cache_regions: raise BeakerException('Cache region `%s` not configured ' @@ -115,6 +120,7 @@ def get_cache_region(name, region): kw = beaker.cache.cache_regions[region] return beaker.cache.Cache._get_cache(name, kw) + def _get_cache_parameters(query): """For a query with cache_region and cache_namespace configured, return the correspoinding Cache instance and cache key, based @@ -122,7 +128,8 @@ def _get_cache_parameters(query): """ if not hasattr(query, '_cache_parameters'): - raise ValueError("This Query does not have caching parameters configured.") + raise ValueError("This Query does not have caching " + "parameters configured.") region, namespace, cache_key = query._cache_parameters @@ -142,6 +149,7 @@ def _get_cache_parameters(query): return cache, cache_key + def _namespace_from_query(namespace, query): # cache namespace - the token handed in by the # option + class we're querying against @@ -152,6 +160,7 @@ def _namespace_from_query(namespace, que return namespace + def _set_cache_parameters(query, region, namespace, cache_key): if hasattr(query, '_cache_parameters'): @@ -162,6 +171,7 @@ def _set_cache_parameters(query, region, ) query._cache_parameters = region, namespace, cache_key + class FromCache(MapperOption): """Specifies that a Query should load results from a cache.""" @@ -191,7 +201,9 @@ class FromCache(MapperOption): def process_query(self, query): """Process a Query during normal loading operation.""" - _set_cache_parameters(query, self.region, self.namespace, self.cache_key) + _set_cache_parameters(query, self.region, self.namespace, + self.cache_key) + class RelationshipCache(MapperOption): """Specifies that a Query as called within a "lazy load" @@ -217,7 +229,7 @@ class RelationshipCache(MapperOption): self.region = region self.namespace = namespace self._relationship_options = { - (attribute.property.parent.class_, attribute.property.key) : self + (attribute.property.parent.class_, attribute.property.key): self } def process_query_conditionally(self, query): @@ -232,7 +244,8 @@ class RelationshipCache(MapperOption): for cls in mapper.class_.__mro__: if (cls, key) in self._relationship_options: - relationship_option = self._relationship_options[(cls, key)] + relationship_option = \ + self._relationship_options[(cls, key)] _set_cache_parameters( query, relationship_option.region, @@ -261,6 +274,7 @@ def _params_from_query(query): """ v = [] + def visit_bindparam(bind): value = query._params.get(bind.key, bind.value) @@ -272,5 +286,5 @@ def _params_from_query(query): v.append(value) if query._criterion is not None: - visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam}) + visitors.traverse(query._criterion, {}, {'bindparam': visit_bindparam}) return v diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -189,6 +189,20 @@ class UsersGroup(Base): members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined") + + @classmethod + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = Session.query(cls)\ + .filter(cls.users_group_name.ilike(group_name)) + else: + gr = Session.query(UsersGroup)\ + .filter(UsersGroup.users_group_name == group_name) + if cache: + gr = gr.options(FromCache("sql_cache_short", + "get_user_%s" % group_name)) + return gr.scalar() + class UsersGroupMember(Base): __tablename__ = 'users_groups_members' __table_args__ = {'useexisting':True} @@ -226,7 +240,7 @@ class Repository(Base): fork = relationship('Repository', remote_side=repo_id) group = relationship('Group') repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UsersGroupToPerm', cascade='all') + users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all') stats = relationship('Statistics', cascade='all', uselist=False) followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all') @@ -377,8 +391,8 @@ class UserToPerm(Base): except: Session.rollback() -class UsersGroupToPerm(Base): - __tablename__ = 'users_group_to_perm' +class UsersGroupRepoToPerm(Base): + __tablename__ = 'users_group_repo_to_perm' __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True}) users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) @@ -389,6 +403,55 @@ class UsersGroupToPerm(Base): permission = relationship('Permission') repository = relationship('Repository') + +class UsersGroupToPerm(Base): + __tablename__ = 'users_group_to_perm' + users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) + users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) + permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) + + users_group = relationship('UsersGroup') + permission = relationship('Permission') + + + @classmethod + def has_perm(cls, users_group_id, perm): + if not isinstance(perm, Permission): + raise Exception('perm needs to be an instance of Permission class') + + return Session.query(cls).filter(cls.users_group_id == + users_group_id)\ + .filter(cls.permission == perm)\ + .scalar() is not None + + @classmethod + def grant_perm(cls, users_group_id, perm): + if not isinstance(perm, Permission): + raise Exception('perm needs to be an instance of Permission class') + + new = cls() + new.users_group_id = users_group_id + new.permission = perm + try: + Session.add(new) + Session.commit() + except: + Session.rollback() + + + @classmethod + def revoke_perm(cls, users_group_id, perm): + if not isinstance(perm, Permission): + raise Exception('perm needs to be an instance of Permission class') + + try: + Session.query(cls).filter(cls.users_group_id == users_group_id)\ + .filter(cls.permission == perm).delete() + Session.commit() + except: + Session.rollback() + + class GroupToPerm(Base): __tablename__ = 'group_to_perm' __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True}) diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -37,7 +37,6 @@ from rhodecode.lib.exceptions import Lda from rhodecode.model import meta from rhodecode.model.user import UserModel from rhodecode.model.repo import RepoModel -from rhodecode.model.users_group import UsersGroupModel from rhodecode.model.db import User, UsersGroup from rhodecode import BACKENDS @@ -47,9 +46,9 @@ log = logging.getLogger(__name__) class State_obj(object): _ = staticmethod(_) -#=============================================================================== +#============================================================================== # VALIDATORS -#=============================================================================== +#============================================================================== class ValidAuthToken(formencode.validators.FancyValidator): messages = {'invalid_token':_('Token mismatch')} @@ -73,23 +72,19 @@ def ValidUsername(edit, old_data): if old_un != value or not edit: if UserModel().get_by_username(value, cache=False, case_insensitive=True): - raise formencode.Invalid(_('This username already exists') , - value, state) - + raise formencode.Invalid(_('This username already ' + 'exists') , value, state) if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: raise formencode.Invalid(_('Username may only contain ' - 'alphanumeric characters underscores, ' - 'periods or dashes and must begin with ' - 'alphanumeric character'), - value, state) - - + 'alphanumeric characters ' + 'underscores, periods or dashes ' + 'and must begin with alphanumeric ' + 'character'), value, state) return _ValidUsername - def ValidUsersGroup(edit, old_data): class _ValidUsersGroup(formencode.validators.FancyValidator): @@ -100,22 +95,23 @@ def ValidUsersGroup(edit, old_data): #check if group is unique old_ugname = None if edit: - old_ugname = UsersGroupModel()\ - .get(old_data.get('users_group_id')).users_group_name + old_ugname = UsersGroup.get( + old_data.get('users_group_id')).users_group_name if old_ugname != value or not edit: - if UsersGroupModel().get_by_groupname(value, cache=False, + if UsersGroup.get_by_group_name(value, cache=False, case_insensitive=True): - raise formencode.Invalid(_('This users group already exists') , - value, state) + raise formencode.Invalid(_('This users group ' + 'already exists') , value, + state) if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: raise formencode.Invalid(_('Group name may only contain ' - 'alphanumeric characters underscores, ' - 'periods or dashes and must begin with ' - 'alphanumeric character'), - value, state) + 'alphanumeric characters ' + 'underscores, periods or dashes ' + 'and must begin with alphanumeric ' + 'character'), value, state) return _ValidUsersGroup diff --git a/rhodecode/model/meta.py b/rhodecode/model/meta.py --- a/rhodecode/model/meta.py +++ b/rhodecode/model/meta.py @@ -19,6 +19,7 @@ Session = scoped_session( ) ) + class BaseModel(object): """Base Model for all classess diff --git a/rhodecode/model/permission.py b/rhodecode/model/permission.py --- a/rhodecode/model/permission.py +++ b/rhodecode/model/permission.py @@ -66,8 +66,10 @@ class PermissionModel(BaseModel): def update(self, form_result): perm_user = self.sa.query(User)\ - .filter(User.username == form_result['perm_user_name']).scalar() - u2p = self.sa.query(UserToPerm).filter(UserToPerm.user == perm_user).all() + .filter(User.username == + form_result['perm_user_name']).scalar() + u2p = self.sa.query(UserToPerm).filter(UserToPerm.user == + perm_user).all() if len(u2p) != 3: raise Exception('Defined: %s should be 3 permissions for default' ' user. This should not happen please verify' @@ -104,7 +106,6 @@ class PermissionModel(BaseModel): perm_user.active = bool(form_result['anonymous']) self.sa.add(perm_user) - self.sa.commit() except (DatabaseError,): log.error(traceback.format_exc()) diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -36,13 +36,12 @@ from vcs.backends import get_backend from rhodecode.model import BaseModel from rhodecode.model.caching_query import FromCache from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \ - Statistics, UsersGroup, UsersGroupToPerm, RhodeCodeUi + Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi from rhodecode.model.user import UserModel -from rhodecode.model.users_group import UsersGroupMember, UsersGroupModel - log = logging.getLogger(__name__) + class RepoModel(BaseModel): @LazyProperty @@ -62,7 +61,6 @@ class RepoModel(BaseModel): "get_repo_%s" % repo_id)) return repo.scalar() - def get_by_repo_name(self, repo_name, cache=False): repo = self.sa.query(Repository)\ .filter(Repository.repo_name == repo_name) @@ -72,7 +70,6 @@ class RepoModel(BaseModel): "get_repo_%s" % repo_name)) return repo.scalar() - def get_full(self, repo_name, cache=False, invalidate=False): repo = self.sa.query(Repository)\ .options(joinedload(Repository.fork))\ @@ -95,7 +92,6 @@ class RepoModel(BaseModel): make_transient(attr) return ret - def get_users_js(self): users = self.sa.query(User).filter(User.active == True).all() @@ -105,7 +101,6 @@ class RepoModel(BaseModel): for u in users]) return users_array - def get_users_groups_js(self): users_groups = self.sa.query(UsersGroup)\ .filter(UsersGroup.users_group_active == True).all() @@ -122,29 +117,30 @@ class RepoModel(BaseModel): try: cur_repo = self.get_by_repo_name(repo_name, cache=False) user_model = UserModel(self.sa) - users_group_model = UsersGroupModel(self.sa) #update permissions for member, perm, member_type in form_data['perms_updates']: if member_type == 'user': r2p = self.sa.query(RepoToPerm)\ - .filter(RepoToPerm.user == user_model.get_by_username(member))\ + .filter(RepoToPerm.user == user_model. + get_by_username(member))\ .filter(RepoToPerm.repository == cur_repo)\ .one() r2p.permission = self.sa.query(Permission)\ - .filter(Permission.permission_name == perm)\ - .scalar() + .filter(Permission.permission_name == + perm).scalar() self.sa.add(r2p) else: - g2p = self.sa.query(UsersGroupToPerm)\ - .filter(UsersGroupToPerm.users_group == users_group_model.get_by_groupname(member))\ - .filter(UsersGroupToPerm.repository == cur_repo)\ - .one() + 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() + .filter(Permission.permission_name == + perm).scalar() self.sa.add(g2p) #set new permissions @@ -155,17 +151,19 @@ class RepoModel(BaseModel): r2p.user = user_model.get_by_username(member) r2p.permission = self.sa.query(Permission)\ - .filter(Permission.permission_name == perm)\ - .scalar() + .filter(Permission. + permission_name == perm)\ + .scalar() self.sa.add(r2p) else: - g2p = UsersGroupToPerm() + g2p = UsersGroupRepoToPerm() g2p.repository = cur_repo - g2p.users_group = users_group_model.get_by_groupname(member) + g2p.users_group = UsersGroup.get_by_group_name(member) g2p.permission = self.sa.query(Permission)\ - .filter(Permission.permission_name == perm)\ - .scalar() + .filter(Permission. + permission_name == perm)\ + .scalar() self.sa.add(g2p) #update current repo @@ -276,10 +274,10 @@ class RepoModel(BaseModel): def delete_perm_users_group(self, form_data, repo_name): try: - self.sa.query(UsersGroupToPerm)\ - .filter(UsersGroupToPerm.repository \ + self.sa.query(UsersGroupRepoToPerm)\ + .filter(UsersGroupRepoToPerm.repository \ == self.get_by_repo_name(repo_name))\ - .filter(UsersGroupToPerm.users_group_id \ + .filter(UsersGroupRepoToPerm.users_group_id \ == form_data['users_group_id']).delete() self.sa.commit() except: @@ -298,7 +296,6 @@ class RepoModel(BaseModel): self.sa.rollback() raise - def __create_repo(self, repo_name, alias, clone_uri=False): """ makes repository on filesystem diff --git a/rhodecode/model/users_group.py b/rhodecode/model/users_group.py --- a/rhodecode/model/users_group.py +++ b/rhodecode/model/users_group.py @@ -32,8 +32,6 @@ from rhodecode.model import BaseModel from rhodecode.model.caching_query import FromCache from rhodecode.model.db import UsersGroup, UsersGroupMember -from sqlalchemy.exc import DatabaseError - log = logging.getLogger(__name__) @@ -43,24 +41,9 @@ class UsersGroupModel(BaseModel): users_group = self.sa.query(UsersGroup) if cache: users_group = users_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % users_group_id)) + "get_users_group_%s" % users_group_id)) return users_group.get(users_group_id) - - def get_by_groupname(self, users_group_name, cache=False, - case_insensitive=False): - - if case_insensitive: - user = self.sa.query(UsersGroup)\ - .filter(UsersGroup.users_group_name.ilike(users_group_name)) - else: - user = self.sa.query(UsersGroup)\ - .filter(UsersGroup.users_group_name == users_group_name) - if cache: - user = user.options(FromCache("sql_cache_short", - "get_user_%s" % users_group_name)) - return user.scalar() - def create(self, form_data): try: new_users_group = UsersGroup() @@ -86,8 +69,9 @@ class UsersGroupModel(BaseModel): members_list = [] if v: for u_id in set(v): - members_list.append(UsersGroupMember(users_group_id, - u_id)) + members_list.append(UsersGroupMember( + users_group_id, + u_id)) setattr(users_group, 'members', members_list) setattr(users_group, k, v) diff --git a/rhodecode/templates/admin/users_groups/users_group_edit.html b/rhodecode/templates/admin/users_groups/users_group_edit.html --- a/rhodecode/templates/admin/users_groups/users_group_edit.html +++ b/rhodecode/templates/admin/users_groups/users_group_edit.html @@ -247,7 +247,7 @@