diff --git a/rhodecode/controllers/api/api.py b/rhodecode/controllers/api/api.py --- a/rhodecode/controllers/api/api.py +++ b/rhodecode/controllers/api/api.py @@ -1,19 +1,8 @@ # -*- coding: utf-8 -*- -""" - rhodecode.controllers.api - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - API controller for RhodeCode - - :created_on: Aug 20, 2011 - :author: marcink - :copyright: (C) 2011-2012 Marcin Kuzminski - :license: GPLv3, see COPYING for more details. -""" -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; version 2 -# of the License or (at your opinion) any later version of the license. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -21,31 +10,46 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. +# along with this program. If not, see . +""" +rhodecode.controllers.api +~~~~~~~~~~~~~~~~~~~~~~~~~ + +API controller for RhodeCode + +:created_on: Aug 20, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + import time import traceback import logging +from sqlalchemy import or_ from rhodecode.controllers.api import JSONRPCController, JSONRPCError -from rhodecode.lib.auth import PasswordGenerator, AuthUser, \ - HasPermissionAllDecorator, HasPermissionAnyDecorator, \ - HasPermissionAnyApi, HasRepoPermissionAnyApi +from rhodecode.lib.auth import ( + PasswordGenerator, AuthUser, HasPermissionAllDecorator, + HasPermissionAnyDecorator, HasPermissionAnyApi, HasRepoPermissionAnyApi, + HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAny) from rhodecode.lib.utils import map_groups, repo2db_mapper -from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int +from rhodecode.lib.utils2 import ( + str2bool, time_to_datetime, safe_int, Optional, OAttr) from rhodecode.model.meta import Session -from rhodecode.model.scm import ScmModel +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.scm import ScmModel, UserGroupList from rhodecode.model.repo import RepoModel from rhodecode.model.user import UserModel -from rhodecode.model.users_group import UserGroupModel -from rhodecode.model.repos_group import ReposGroupModel -from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\ - Permission, User, Gist +from rhodecode.model.user_group import UserGroupModel +from rhodecode.model.gist import GistModel +from rhodecode.model.db import ( + Repository, RhodeCodeSetting, UserIpMap, Permission, User, Gist, + RepoGroup) from rhodecode.lib.compat import json -from rhodecode.lib.exceptions import DefaultUserException -from rhodecode.model.gist import GistModel +from rhodecode.lib.exceptions import ( + DefaultUserException, UserGroupsAssignedException) log = logging.getLogger(__name__) @@ -59,56 +63,6 @@ def store_update(updates, attr, name): updates[name] = attr -class OptionalAttr(object): - """ - Special Optional Option that defines other attribute - """ - def __init__(self, attr_name): - self.attr_name = attr_name - - def __repr__(self): - return '' % self.attr_name - - def __call__(self): - return self -#alias -OAttr = OptionalAttr - - -class Optional(object): - """ - Defines an optional parameter:: - - param = param.getval() if isinstance(param, Optional) else param - param = param() if isinstance(param, Optional) else param - - is equivalent of:: - - param = Optional.extract(param) - - """ - def __init__(self, type_): - self.type_ = type_ - - def __repr__(self): - return '' % self.type_.__repr__() - - def __call__(self): - return self.getval() - - def getval(self): - """ - returns value from this Optional instance - """ - return self.type_ - - @classmethod - def extract(cls, val): - if isinstance(val, cls): - return val.getval() - return val - - def get_user_or_error(userid): """ Get user by id or name or return JsonRPCError if not found @@ -117,7 +71,7 @@ def get_user_or_error(userid): """ user = UserModel().get_user(userid) if user is None: - raise JSONRPCError("user `%s` does not exist" % userid) + raise JSONRPCError("user `%s` does not exist" % (userid,)) return user @@ -129,7 +83,7 @@ def get_repo_or_error(repoid): """ repo = RepoModel().get_repo(repoid) if repo is None: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) return repo @@ -139,60 +93,103 @@ def get_repo_group_or_error(repogroupid) :param repogroupid: """ - repo_group = ReposGroupModel()._get_repo_group(repogroupid) + repo_group = RepoGroupModel()._get_repo_group(repogroupid) if repo_group is None: raise JSONRPCError( 'repository group `%s` does not exist' % (repogroupid,)) return repo_group -def get_users_group_or_error(usersgroupid): +def get_user_group_or_error(usergroupid): """ Get user group by id or name or return JsonRPCError if not found - :param userid: + :param usergroupid: """ - users_group = UserGroupModel().get_group(usersgroupid) - if users_group is None: - raise JSONRPCError('user group `%s` does not exist' % usersgroupid) - return users_group + user_group = UserGroupModel().get_group(usergroupid) + if user_group is None: + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) + return user_group -def get_perm_or_error(permid): +def get_perm_or_error(permid, prefix=None): """ Get permission by id or name or return JsonRPCError if not found - :param userid: + :param permid: """ perm = Permission.get_by_key(permid) if perm is None: - raise JSONRPCError('permission `%s` does not exist' % (permid)) + raise JSONRPCError('permission `%s` does not exist' % (permid,)) + if prefix: + if not perm.permission_name.startswith(prefix): + raise JSONRPCError('permission `%s` is invalid, ' + 'should start with %s' % (permid, prefix)) return perm +def get_gist_or_error(gistid): + """ + Get gist by id or gist_access_id or return JsonRPCError if not found + + :param gistid: + """ + gist = GistModel().get_gist(gistid) + if gist is None: + raise JSONRPCError('gist `%s` does not exist' % (gistid,)) + return gist + + class ApiController(JSONRPCController): """ API Controller + Each method takes USER as first argument. This is then, based on given + API_KEY propagated as instance of user object who's making the call. - Each method needs to have USER as argument this is then based on given - API_KEY propagated as instance of user object + example function:: - Preferably this should be first argument also - + def func(apiuser,arg1, arg2,...): + pass Each function should also **raise** JSONRPCError for any - errors that happens + errors that happens. """ @HasPermissionAllDecorator('hg.admin') + def test(self, apiuser, args): + return args + + @HasPermissionAllDecorator('hg.admin') def pull(self, apiuser, repoid): """ - Dispatch pull action on given repo + Triggers a pull from remote location on given repo. Can be used to + automatically keep remote repos up to date. This command can be executed + only using api_key belonging to user with admin rights + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + + OUTPUT:: - :param apiuser: - :param repoid: + id : + result : { + "msg": "Pulled from ``" + "repository": "" + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "Unable to pull changes from ``" + } + """ repo = get_repo_or_error(repoid) @@ -200,7 +197,10 @@ class ApiController(JSONRPCController): try: ScmModel().pull_changes(repo.repo_name, self.rhodecode_user.username) - return 'Pulled from `%s`' % repo.repo_name + return dict( + msg='Pulled from `%s`' % repo.repo_name, + repository=repo.repo_name + ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( @@ -210,12 +210,34 @@ class ApiController(JSONRPCController): @HasPermissionAllDecorator('hg.admin') def rescan_repos(self, apiuser, remove_obsolete=Optional(False)): """ - Dispatch rescan repositories action. If remove_obsolete is set + Triggers rescan repositories action. If remove_obsolete is set than also delete repos that are in database but not in the filesystem. - aka "clean zombies" + aka "clean zombies". This command can be executed only using api_key + belonging to user with admin rights. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param remove_obsolete: deletes repositories from + database that are not found on the filesystem + :type remove_obsolete: Optional(bool) + + OUTPUT:: - :param apiuser: - :param remove_obsolete: + id : + result : { + 'added': [,...] + 'removed': [,...] + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + 'Error occurred during rescan repositories action' + } + """ try: @@ -231,22 +253,47 @@ class ApiController(JSONRPCController): def invalidate_cache(self, apiuser, repoid): """ - Dispatch cache invalidation action on given repo + Invalidate cache for repository. + This command can be executed only using api_key belonging to user with admin + rights or regular user that have write or admin or write access to repository. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + + OUTPUT:: - :param apiuser: - :param repoid: + id : + result : { + 'msg': Cache for repository `` was invalidated, + 'repository': + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + 'Error occurred during cache invalidation action' + } + """ repo = get_repo_or_error(repoid) if not HasPermissionAnyApi('hg.admin')(user=apiuser): # check if we have admin permission for this repo ! - if HasRepoPermissionAnyApi('repository.admin', - 'repository.write')(user=apiuser, - repo_name=repo.repo_name) is False: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + if not HasRepoPermissionAnyApi('repository.admin', + 'repository.write')( + user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) try: ScmModel().mark_for_invalidation(repo.repo_name) - return ('Caches of repository `%s` was invalidated' % repoid) + return dict( + msg='Cache for repository `%s` was invalidated' % (repoid,), + repository=repo.repo_name + ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( @@ -257,14 +304,47 @@ class ApiController(JSONRPCController): def lock(self, apiuser, repoid, locked=Optional(None), userid=Optional(OAttr('apiuser'))): """ - Set locking state on particular repository by given user, if - this command is runned by non-admin account userid is set to user - who is calling this method + Set locking state on given repository by given user. If userid param + is skipped, then it is set to id of user whos calling this method. + If locked param is skipped then function shows current lock state of + given repo. This command can be executed only using api_key belonging + to user with admin rights or regular user that have admin or write + access to repository. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param locked: lock state to be set + :type locked: Optional(bool) + :param userid: set lock as user + :type userid: Optional(str or int) + + OUTPUT:: - :param apiuser: - :param repoid: - :param userid: - :param locked: + id : + result : { + 'repo': '', + 'locked': , + 'locked_since': , + 'locked_by': , + 'lock_state_changed': , + 'msg': 'Repo `` locked by `` on .' + or + 'msg': 'Repo `` not locked.' + or + 'msg': 'User `` set lock state for repo `` to ``' + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + 'Error occurred locking repository `` + } + """ repo = get_repo_or_error(repoid) if HasPermissionAnyApi('hg.admin')(user=apiuser): @@ -272,14 +352,14 @@ class ApiController(JSONRPCController): elif HasRepoPermissionAnyApi('repository.admin', 'repository.write')(user=apiuser, repo_name=repo.repo_name): - #make sure normal user does not pass someone else userid, - #he is not allowed to do that + # make sure normal user does not pass someone else userid, + # he is not allowed to do that if not isinstance(userid, Optional) and userid != apiuser.user_id: raise JSONRPCError( 'userid is not the same as your user' ) else: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) if isinstance(userid, Optional): userid = apiuser.user_id @@ -295,6 +375,7 @@ class ApiController(JSONRPCController): 'locked': False, 'locked_since': None, 'locked_by': None, + 'lock_state_changed': False, 'msg': 'Repo `%s` not locked.' % repo.repo_name } return _d @@ -306,8 +387,9 @@ class ApiController(JSONRPCController): 'locked': True, 'locked_since': time_, 'locked_by': lock_user.username, - 'msg': ('Repo `%s` locked by `%s`. ' - % (repo.repo_name, + 'lock_state_changed': False, + 'msg': ('Repo `%s` locked by `%s` on `%s`.' + % (repo.repo_name, lock_user.username, json.dumps(time_to_datetime(time_)))) } return _d @@ -327,6 +409,7 @@ class ApiController(JSONRPCController): 'locked': locked, 'locked_since': lock_time, 'locked_by': user.username, + 'lock_state_changed': True, 'msg': ('User `%s` set lock state for repo `%s` to `%s`' % (user.username, repo.repo_name, locked)) } @@ -339,28 +422,39 @@ class ApiController(JSONRPCController): def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))): """ - Get all locks for given userid, if + Get all repositories with locks for given userid, if this command is runned by non-admin account userid is set to user - who is calling this method, thus returning locks for himself + who is calling this method, thus returning locks for himself. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: User to get locks for + :type userid: Optional(str or int) - :param apiuser: - :param userid: + OUTPUT:: + + id : + result : { + [repo_object, repo_object,...] + } + error : null """ if not HasPermissionAnyApi('hg.admin')(user=apiuser): - #make sure normal user does not pass someone else userid, - #he is not allowed to do that + # make sure normal user does not pass someone else userid, + # he is not allowed to do that if not isinstance(userid, Optional) and userid != apiuser.user_id: raise JSONRPCError( 'userid is not the same as your user' ) + ret = [] if isinstance(userid, Optional): user = None else: user = get_user_or_error(userid) - #show all locks + # show all locks for r in Repository.getAll(): userid, time_ = r.locked if time_: @@ -375,31 +469,114 @@ class ApiController(JSONRPCController): return ret @HasPermissionAllDecorator('hg.admin') - def show_ip(self, apiuser, userid): + def get_ip(self, apiuser, userid=Optional(OAttr('apiuser'))): """ Shows IP address as seen from RhodeCode server, together with all - defined IP addresses for given user + defined IP addresses for given user. If userid is not passed data is + returned for user who's calling this function. + This command can be executed only using api_key belonging to user with + admin rights. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: username to show ips for + :type userid: Optional(str or int) + + OUTPUT:: - :param apiuser: - :param userid: + id : + result : { + "server_ip_addr": "", + "user_ips": [ + { + "ip_addr": "", + "ip_range": ["", ""], + }, + ... + ] + } + """ + if isinstance(userid, Optional): + userid = apiuser.user_id user = get_user_or_error(userid) ips = UserIpMap.query().filter(UserIpMap.user == user).all() return dict( - ip_addr_server=self.ip_addr, + server_ip_addr=self.ip_addr, user_ips=ips ) + # alias for old + show_ip = get_ip + + @HasPermissionAllDecorator('hg.admin') + def get_server_info(self, apiuser): + """ + return server info, including RhodeCode version and installed packages + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: + + id : + result : { + 'modules': [,...] + 'py_version': , + 'platform': , + 'rhodecode_version': + } + error : null + """ + return RhodeCodeSetting.get_server_info() + def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))): - """" - Get a user by username, or userid, if userid is given + """ + Get's an user by username or user_id, Returns empty result if user is + not found. If userid param is skipped it is set to id of user who is + calling this method. This command can be executed only using api_key + belonging to user with admin rights, or regular users that cannot + specify different userid than theirs + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: user to get data for + :type userid: Optional(str or int) + + OUTPUT:: - :param apiuser: - :param userid: + id : + result: None if user does not exist or + { + "user_id" : "", + "api_key" : "", + "api_keys": "[]" + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "emails": "[]", + "ip_addresses": "[,...]", + "active" : "", + "admin" :  "", + "extern_name" : "", + "extern_type" : " + "last_login": "", + "permissions": { + "global": ["hg.create.repository", + "repository.read", + "hg.register.manual_activate"], + "repositories": {"repo1": "repository.none"}, + "repositories_groups": {"Group1": "group.read"} + }, + } + + error: null + """ if not HasPermissionAnyApi('hg.admin')(user=apiuser): - #make sure normal user does not pass someone else userid, - #he is not allowed to do that + # make sure normal user does not pass someone else userid, + # he is not allowed to do that if not isinstance(userid, Optional) and userid != apiuser.user_id: raise JSONRPCError( 'userid is not the same as your user' @@ -415,47 +592,91 @@ class ApiController(JSONRPCController): @HasPermissionAllDecorator('hg.admin') def get_users(self, apiuser): - """" - Get all users + """ + Lists all existing users. This command can be executed only using api_key + belonging to user with admin rights. - :param apiuser: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: + + id : + result: [, ...] + error: null """ result = [] - users_list = User.query().order_by(User.username)\ - .filter(User.username != User.DEFAULT_USER)\ - .all() + users_list = User.query().order_by(User.username) \ + .filter(User.username != User.DEFAULT_USER) \ + .all() for user in users_list: result.append(user.get_api_data()) return result @HasPermissionAllDecorator('hg.admin') - def create_user(self, apiuser, username, email, password=Optional(None), - firstname=Optional(None), lastname=Optional(None), + def create_user(self, apiuser, username, email, password=Optional(''), + firstname=Optional(''), lastname=Optional(''), active=Optional(True), admin=Optional(False), - ldap_dn=Optional(None)): + extern_name=Optional('rhodecode'), + extern_type=Optional('rhodecode')): """ - Create new user + Creates new user. Returns new user object. This command can + be executed only using api_key belonging to user with admin rights. - :param apiuser: - :param username: - :param email: - :param password: - :param firstname: - :param lastname: - :param active: - :param admin: - :param ldap_dn: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param username: new username + :type username: str or int + :param email: email + :type email: str + :param password: password + :type password: Optional(str) + :param firstname: firstname + :type firstname: Optional(str) + :param lastname: lastname + :type lastname: Optional(str) + :param active: active + :type active: Optional(bool) + :param admin: admin + :type admin: Optional(bool) + :param extern_name: name of extern + :type extern_name: Optional(str) + :param extern_type: extern_type + :type extern_type: Optional(str) + + + OUTPUT:: + + id : + result: { + "msg" : "created new user ``", + "user": + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "user `` already exist" + or + "email `` already exist" + or + "failed to create user ``" + } + """ if UserModel().get_by_username(username): - raise JSONRPCError("user `%s` already exist" % username) + raise JSONRPCError("user `%s` already exist" % (username,)) if UserModel().get_by_email(email, case_insensitive=True): - raise JSONRPCError("email `%s` already exist" % email) + raise JSONRPCError("email `%s` already exist" % (email,)) - if Optional.extract(ldap_dn): - # generate temporary password if ldap_dn + if Optional.extract(extern_name): + # generate temporary password if user is external password = PasswordGenerator().gen_password(length=8) try: @@ -467,7 +688,8 @@ class ApiController(JSONRPCController): lastname=Optional.extract(lastname), active=Optional.extract(active), admin=Optional.extract(admin), - ldap_dn=Optional.extract(ldap_dn) + extern_type=Optional.extract(extern_type), + extern_name=Optional.extract(extern_name) ) Session().commit() return dict( @@ -476,48 +698,77 @@ class ApiController(JSONRPCController): ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create user `%s`' % username) + raise JSONRPCError('failed to create user `%s`' % (username,)) @HasPermissionAllDecorator('hg.admin') def update_user(self, apiuser, userid, username=Optional(None), - email=Optional(None), firstname=Optional(None), - lastname=Optional(None), active=Optional(None), - admin=Optional(None), ldap_dn=Optional(None), - password=Optional(None)): + email=Optional(None),password=Optional(None), + firstname=Optional(None), lastname=Optional(None), + active=Optional(None), admin=Optional(None), + extern_type=Optional(None), extern_name=Optional(None),): """ - Updates given user + updates given user if such user exists. This command can + be executed only using api_key belonging to user with admin rights. - :param apiuser: - :param userid: - :param username: - :param email: - :param firstname: - :param lastname: - :param active: - :param admin: - :param ldap_dn: - :param password: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: userid to update + :type userid: str or int + :param username: new username + :type username: str or int + :param email: email + :type email: str + :param password: password + :type password: Optional(str) + :param firstname: firstname + :type firstname: Optional(str) + :param lastname: lastname + :type lastname: Optional(str) + :param active: active + :type active: Optional(bool) + :param admin: admin + :type admin: Optional(bool) + :param extern_name: + :type extern_name: Optional(str) + :param extern_type: + :type extern_type: Optional(str) + + + OUTPUT:: + + id : + result: { + "msg" : "updated user ID: ", + "user": , + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to update user ``" + } + """ user = get_user_or_error(userid) - # call function and store only updated arguments + # only non optional arguments will be stored in updates updates = {} - def store_update(attr, name): - if not isinstance(attr, Optional): - updates[name] = attr - try: - store_update(username, 'username') - store_update(password, 'password') - store_update(email, 'email') - store_update(firstname, 'name') - store_update(lastname, 'lastname') - store_update(active, 'active') - store_update(admin, 'admin') - store_update(ldap_dn, 'ldap_dn') + store_update(updates, username, 'username') + store_update(updates, password, 'password') + store_update(updates, email, 'email') + store_update(updates, firstname, 'name') + store_update(updates, lastname, 'lastname') + store_update(updates, active, 'active') + store_update(updates, admin, 'admin') + store_update(updates, extern_name, 'extern_name') + store_update(updates, extern_type, 'extern_type') user = UserModel().update_user(user, **updates) Session().commit() @@ -530,15 +781,36 @@ class ApiController(JSONRPCController): raise JSONRPCError('editing default user is forbidden') except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to update user `%s`' % userid) + raise JSONRPCError('failed to update user `%s`' % (userid,)) @HasPermissionAllDecorator('hg.admin') def delete_user(self, apiuser, userid): - """" - Deletes an user + """ + deletes givenuser if such user exists. This command can + be executed only using api_key belonging to user with admin rights. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: user to delete + :type userid: str or int + + OUTPUT:: - :param apiuser: - :param userid: + id : + result: { + "msg" : "deleted user ID: ", + "user": null + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete user ID: " + } + """ user = get_user_or_error(userid) @@ -550,57 +822,114 @@ class ApiController(JSONRPCController): user=None ) except Exception: + log.error(traceback.format_exc()) - raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id, - user.username)) + raise JSONRPCError('failed to delete user ID:%s %s' + % (user.user_id, user.username)) - @HasPermissionAllDecorator('hg.admin') - def get_users_group(self, apiuser, usersgroupid): - """" - Get user group by name or id + # permission check inside + def get_user_group(self, apiuser, usergroupid): + """ + Gets an existing user group. This command can be executed only using api_key + belonging to user with admin rights or user who has at least + read access to user group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: id of user_group to edit + :type usergroupid: str or int + + OUTPUT:: - :param apiuser: - :param usersgroupid: - """ - users_group = get_users_group_or_error(usersgroupid) + id : + result : None if group not exist + { + "users_group_id" : "", + "group_name" : "", + "active": "", + "members" : [,...] + } + error : null - data = users_group.get_api_data() + """ + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) - members = [] - for user in users_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members + data = user_group.get_api_data() return data - @HasPermissionAllDecorator('hg.admin') - def get_users_groups(self, apiuser): - """" - Get all user groups + # permission check inside + def get_user_groups(self, apiuser): + """ + Lists all existing user groups. This command can be executed only using + api_key belonging to user with admin rights or user who has at least + read access to user group. - :param apiuser: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: + + id : + result : [,...] + error : null """ result = [] - for users_group in UserGroupModel().get_all(): - result.append(users_group.get_api_data()) + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + extras = {'user': apiuser} + for user_group in UserGroupList(UserGroupModel().get_all(), + perm_set=_perms, extra_kwargs=extras): + result.append(user_group.get_api_data()) return result - @HasPermissionAllDecorator('hg.admin') - def create_users_group(self, apiuser, group_name, - owner=Optional(OAttr('apiuser')), - active=Optional(True)): + @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') + def create_user_group(self, apiuser, group_name, description=Optional(''), + owner=Optional(OAttr('apiuser')), active=Optional(True)): """ - Creates an new usergroup + Creates new user group. This command can be executed only using api_key + belonging to user with admin rights or an user who has create user group + permission + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param group_name: name of new user group + :type group_name: str + :param description: group description + :type description: str + :param owner: owner of group. If not passed apiuser is the owner + :type owner: Optional(str or int) + :param active: group is active + :type active: Optional(bool) + + OUTPUT:: - :param apiuser: - :param group_name: - :param owner: - :param active: + id : + result: { + "msg": "created new user group ``", + "user_group": + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "user group `` already exist" + or + "failed to create group ``" + } + """ if UserGroupModel().get_by_name(group_name): - raise JSONRPCError("user group `%s` already exist" % group_name) + raise JSONRPCError("user group `%s` already exist" % (group_name,)) try: if isinstance(owner, Optional): @@ -608,36 +937,191 @@ class ApiController(JSONRPCController): owner = get_user_or_error(owner) active = Optional.extract(active) - ug = UserGroupModel().create(name=group_name, - owner=owner, - active=active) + description = Optional.extract(description) + ug = UserGroupModel().create(name=group_name, description=description, + owner=owner, active=active) Session().commit() return dict( msg='created new user group `%s`' % group_name, - users_group=ug.get_api_data() + user_group=ug.get_api_data() + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to create group `%s`' % (group_name,)) + + # permission check inside + def update_user_group(self, apiuser, usergroupid, group_name=Optional(''), + description=Optional(''), owner=Optional(None), + active=Optional(True)): + """ + Updates given usergroup. This command can be executed only using api_key + belonging to user with admin rights or an admin of given user group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: id of user group to update + :type usergroupid: str or int + :param group_name: name of new user group + :type group_name: str + :param description: group description + :type description: str + :param owner: owner of group. + :type owner: Optional(str or int) + :param active: group is active + :type active: Optional(bool) + + OUTPUT:: + + id : + result : { + "msg": 'updated user group ID: ', + "user_group": + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to update user group ``" + } + + """ + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) + + if not isinstance(owner, Optional): + owner = get_user_or_error(owner) + + updates = {} + store_update(updates, group_name, 'users_group_name') + store_update(updates, description, 'user_group_description') + store_update(updates, owner, 'user') + store_update(updates, active, 'users_group_active') + try: + UserGroupModel().update(user_group, updates) + Session().commit() + return dict( + msg='updated user group ID:%s %s' % (user_group.users_group_id, + user_group.users_group_name), + user_group=user_group.get_api_data() ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create group `%s`' % group_name) + raise JSONRPCError('failed to update user group `%s`' % (usergroupid,)) + + # permission check inside + def delete_user_group(self, apiuser, usergroupid): + """ + Delete given user group by user group id or name. + This command can be executed only using api_key + belonging to user with admin rights or an admin of given user group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: + :type usergroupid: int + + OUTPUT:: + + id : + result : { + "msg": "deleted user group ID: " + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete user group ID: " + or + "RepoGroup assigned to " + } + + """ + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) - @HasPermissionAllDecorator('hg.admin') - def add_user_to_users_group(self, apiuser, usersgroupid, userid): - """" - Add a user to a user group + try: + UserGroupModel().delete(user_group) + Session().commit() + return dict( + msg='deleted user group ID:%s %s' % + (user_group.users_group_id, user_group.users_group_name), + user_group=None + ) + except UserGroupsAssignedException, e: + log.error(traceback.format_exc()) + raise JSONRPCError(str(e)) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to delete user group ID:%s %s' % + (user_group.users_group_id, + user_group.users_group_name) + ) + + # permission check inside + def add_user_to_user_group(self, apiuser, usergroupid, userid): + """ + Adds a user to a user group. If user exists in that group success will be + `false`. This command can be executed only using api_key + belonging to user with admin rights or an admin of given user group - :param apiuser: - :param usersgroupid: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: + :type usergroupid: int :param userid: + :type userid: int + + OUTPUT:: + + id : + result : { + "success": True|False # depends on if member is in group + "msg": "added member `` to user group `` | + User is already in that group" + + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to add member to user group ``" + } + """ user = get_user_or_error(userid) - users_group = get_users_group_or_error(usersgroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: - ugm = UserGroupModel().add_user_to_group(users_group, user) + ugm = UserGroupModel().add_user_to_group(user_group, user) success = True if ugm != True else False msg = 'added member `%s` to user group `%s`' % ( - user.username, users_group.users_group_name - ) + user.username, user_group.users_group_name + ) msg = msg if success else 'User is already in that group' Session().commit() @@ -649,28 +1133,48 @@ class ApiController(JSONRPCController): log.error(traceback.format_exc()) raise JSONRPCError( 'failed to add member to user group `%s`' % ( - users_group.users_group_name + user_group.users_group_name, ) ) - @HasPermissionAllDecorator('hg.admin') - def remove_user_from_users_group(self, apiuser, usersgroupid, userid): + # permission check inside + def remove_user_from_user_group(self, apiuser, usergroupid, userid): """ - Remove user from a group + Removes a user from a user group. If user is not in given group success will + be `false`. This command can be executed only + using api_key belonging to user with admin rights or an admin of given user group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param usergroupid: + :param userid: + - :param apiuser: - :param usersgroupid: - :param userid: + OUTPUT:: + + id : + result: { + "success": True|False, # depends on if member is in group + "msg": "removed member from user group | + User wasn't in group" + } + error: null + """ user = get_user_or_error(userid) - users_group = get_users_group_or_error(usersgroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this user group ! + _perms = ('usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: - success = UserGroupModel().remove_user_from_group(users_group, - user) + success = UserGroupModel().remove_user_from_group(user_group, user) msg = 'removed member `%s` from user group `%s`' % ( - user.username, users_group.users_group_name - ) + user.username, user_group.users_group_name + ) msg = msg if success else "User wasn't in group" Session().commit() return dict(success=success, msg=msg) @@ -678,42 +1182,99 @@ class ApiController(JSONRPCController): log.error(traceback.format_exc()) raise JSONRPCError( 'failed to remove member from user group `%s`' % ( - users_group.users_group_name - ) + user_group.users_group_name, + ) ) + # permission check inside def get_repo(self, apiuser, repoid): - """" - Get repository by name + """ + Gets an existing repository by it's name or repository_id. Members will return + either users_group or user associated to that repository. This command can be + executed only using api_key belonging to user with admin + rights or regular user that have at least read access to repository. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + + OUTPUT:: - :param apiuser: - :param repoid: + id : + result : { + { + "repo_id" : "", + "repo_name" : "" + "repo_type" : "", + "clone_uri" : "", + "enable_downloads": "", + "enable_locking": "", + "enable_statistics": "", + "private": "", + "created_on" : "", + "description" : "", + "landing_rev": "", + "last_changeset": { + "author": "", + "date": "", + "message": "", + "raw_id": "", + "revision": "", + "short_id": "" + } + "owner": "", + "fork_of": "", + "members" : [ + { + "name": "", + "type" : "user", + "permission" : "repository.(read|write|admin)" + }, + … + { + "name": "", + "type" : "user_group", + "permission" : "usergroup.(read|write|admin)" + }, + … + ] + "followers": [, ...] + ] + } + } + error : null + """ repo = get_repo_or_error(repoid) if not HasPermissionAnyApi('hg.admin')(user=apiuser): # check if we have admin permission for this repo ! - if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser, - repo_name=repo.repo_name): - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + perms = ('repository.admin', 'repository.write', 'repository.read') + if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) members = [] followers = [] for user in repo.repo_to_perm: perm = user.permission.permission_name user = user.user - user_data = user.get_api_data() - user_data['type'] = "user" - user_data['permission'] = perm + user_data = { + 'name': user.username, + 'type': "user", + 'permission': perm + } members.append(user_data) - for users_group in repo.users_group_to_perm: - perm = users_group.permission.permission_name - users_group = users_group.users_group - users_group_data = users_group.get_api_data() - users_group_data['type'] = "users_group" - users_group_data['permission'] = perm - members.append(users_group_data) + for user_group in repo.users_group_to_perm: + perm = user_group.permission.permission_name + user_group = user_group.users_group + user_group_data = { + 'name': user_group.users_group_name, + 'type': "user_group", + 'permission': perm + } + members.append(user_group_data) for user in repo.followers: followers.append(user.user.get_api_data()) @@ -725,13 +1286,39 @@ class ApiController(JSONRPCController): # permission check inside def get_repos(self, apiuser): - """" - Get all repositories + """ + Lists all existing repositories. This command can be executed only using + api_key belonging to user with admin rights or regular user that have + admin, write or read access to repository. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + + OUTPUT:: - :param apiuser: + id : + result: [ + { + "repo_id" : "", + "repo_name" : "" + "repo_type" : "", + "clone_uri" : "", + "private": : "", + "created_on" : "", + "description" : "", + "landing_rev": "", + "owner": "", + "fork_of": "", + "enable_downloads": "", + "enable_locking": "", + "enable_statistics": "", + }, + … + ] + error: null """ result = [] - if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: + if not HasPermissionAnyApi('hg.admin')(user=apiuser): repos = RepoModel().get_all_user_repos(user=apiuser) else: repos = RepoModel().get_all() @@ -740,21 +1327,49 @@ class ApiController(JSONRPCController): result.append(repo.get_api_data()) return result - @HasPermissionAllDecorator('hg.admin') + # permission check inside def get_repo_nodes(self, apiuser, repoid, revision, root_path, - ret_type='all'): + ret_type=Optional('all')): """ - returns a list of nodes and it's children - for a given path at given revision. It's possible to specify ret_type - to show only files or dirs + returns a list of nodes and it's children in a flat list for a given path + at given revision. It's possible to specify ret_type to show only `files` or + `dirs`. This command can be executed only using api_key belonging to + user with admin rights or regular user that have at least read access to repository. - :param apiuser: - :param repoid: name or id of repository + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int :param revision: revision for which listing should be done + :type revision: str :param root_path: path from which start displaying + :type root_path: str :param ret_type: return type 'all|files|dirs' nodes + :type ret_type: Optional(str) + + + OUTPUT:: + + id : + result: [ + { + "name" : "" + "type" : "", + }, + … + ] + error: null """ repo = get_repo_or_error(repoid) + + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo ! + perms = ('repository.admin', 'repository.write', 'repository.read') + if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) + + ret_type = Optional.extract(ret_type) + _map = {} try: _d, _f = ScmModel().get_nodes(repo, revision, root_path, flat=False) @@ -765,7 +1380,8 @@ class ApiController(JSONRPCController): } return _map[ret_type] except KeyError: - raise JSONRPCError('ret_type must be one of %s' % _map.keys()) + raise JSONRPCError('ret_type must be one of %s' + % (','.join(_map.keys()))) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( @@ -774,27 +1390,67 @@ class ApiController(JSONRPCController): @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')), - repo_type=Optional('hg'), - description=Optional(''), private=Optional(False), - clone_uri=Optional(None), landing_rev=Optional('tip'), + repo_type=Optional('hg'), description=Optional(''), + private=Optional(False), clone_uri=Optional(None), + landing_rev=Optional('rev:tip'), enable_statistics=Optional(False), enable_locking=Optional(False), - enable_downloads=Optional(False)): + enable_downloads=Optional(False), + copy_permissions=Optional(False)): """ - Create repository, if clone_url is given it makes a remote clone - if repo_name is within a group name the groups will be created - automatically if they aren't present + Creates a repository. If repository name contains "/", all needed repository + groups will be created. For example "foo/bar/baz" will create groups + "foo", "bar" (with "foo" as parent), and create "baz" repository with + "bar" as group. This command can be executed only using api_key + belonging to user with admin rights or regular user that have create + repository permission. Regular users cannot specify owner parameter - :param apiuser: - :param repo_name: - :param onwer: - :param repo_type: - :param description: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repo_name: repository name + :type repo_name: str + :param owner: user_id or username + :type owner: Optional(str) + :param repo_type: 'hg' or 'git' + :type repo_type: Optional(str) + :param description: repository description + :type description: Optional(str) :param private: + :type private: bool :param clone_uri: - :param landing_rev: + :type clone_uri: str + :param landing_rev: : + :type landing_rev: str + :param enable_locking: + :type enable_locking: bool + :param enable_downloads: + :type enable_downloads: bool + :param enable_statistics: + :type enable_statistics: bool + :param copy_permissions: Copy permission from group that repository is + beeing created. + :type copy_permissions: bool + + OUTPUT:: + + id : + result: { + "msg": "Created new repository ``", + "success": true, + "task": "" + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + 'failed to create repository `` + } + """ - if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: + if not HasPermissionAnyApi('hg.admin')(user=apiuser): if not isinstance(owner, Optional): #forbid setting owner for non-admins raise JSONRPCError( @@ -823,40 +1479,51 @@ class ApiController(JSONRPCController): clone_uri = Optional.extract(clone_uri) description = Optional.extract(description) landing_rev = Optional.extract(landing_rev) + copy_permissions = Optional.extract(copy_permissions) try: + repo_name_cleaned = repo_name.split('/')[-1] # create structure of groups and return the last group - group = map_groups(repo_name) - - repo = RepoModel().create_repo( - repo_name=repo_name, + repo_group = map_groups(repo_name) + data = dict( + repo_name=repo_name_cleaned, + repo_name_full=repo_name, repo_type=repo_type, - description=description, + repo_description=description, owner=owner, - private=private, + repo_private=private, clone_uri=clone_uri, - repos_group=group, - landing_rev=landing_rev, + repo_group=repo_group, + repo_landing_rev=landing_rev, enable_statistics=enable_statistics, + enable_locking=enable_locking, enable_downloads=enable_downloads, - enable_locking=enable_locking + repo_copy_permissions=copy_permissions, ) - Session().commit() + task = RepoModel().create(form_data=data, cur_user=owner) + from celery.result import BaseAsyncResult + task_id = None + if isinstance(task, BaseAsyncResult): + task_id = task.task_id + # no commit, it's done in RepoModel, or async via celery return dict( - msg="Created new repository `%s`" % (repo.repo_name), - repo=repo.get_api_data() + msg="Created new repository `%s`" % (repo_name,), + success=True, # cannot return the repo data here since fork + # cann be done async + task=task_id ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create repository `%s`' % repo_name) + raise JSONRPCError( + 'failed to create repository `%s`' % (repo_name,)) # permission check inside def update_repo(self, apiuser, repoid, name=Optional(None), owner=Optional(OAttr('apiuser')), group=Optional(None), description=Optional(''), private=Optional(False), - clone_uri=Optional(None), landing_rev=Optional('tip'), + clone_uri=Optional(None), landing_rev=Optional('rev:tip'), enable_statistics=Optional(False), enable_locking=Optional(False), enable_downloads=Optional(False)): @@ -917,9 +1584,53 @@ class ApiController(JSONRPCController): raise JSONRPCError('failed to update repo `%s`' % repoid) @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') - def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')), + def fork_repo(self, apiuser, repoid, fork_name, + owner=Optional(OAttr('apiuser')), description=Optional(''), copy_permissions=Optional(False), - private=Optional(False), landing_rev=Optional('tip')): + private=Optional(False), landing_rev=Optional('rev:tip')): + """ + Creates a fork of given repo. In case of using celery this will + immidiatelly return success message, while fork is going to be created + asynchronous. This command can be executed only using api_key belonging to + user with admin rights or regular user that have fork permission, and at least + read access to forking repository. Regular users cannot specify owner parameter. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param fork_name: + :param owner: + :param description: + :param copy_permissions: + :param private: + :param landing_rev: + + INPUT:: + + id : + api_key : "" + args: { + "repoid" : "", + "fork_name": "", + "owner": "", + "description": "", + "copy_permissions": "", + "private": "", + "landing_rev": "" + } + + OUTPUT:: + + id : + result: { + "msg": "Created fork of `` as ``", + "success": true, + "task": "" + } + error: null + + """ repo = get_repo_or_error(repoid) repo_name = repo.repo_name @@ -940,7 +1651,7 @@ class ApiController(JSONRPCController): 'Only RhodeCode admin can specify `owner` param' ) else: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) if isinstance(owner, Optional): owner = apiuser.user_id @@ -963,12 +1674,18 @@ class ApiController(JSONRPCController): update_after_clone=False, fork_parent_id=repo.repo_id, ) - RepoModel().create_fork(form_data, cur_user=owner) + task = RepoModel().create_fork(form_data, cur_user=owner) + # no commit, it's done in RepoModel, or async via celery + from celery.result import BaseAsyncResult + task_id = None + if isinstance(task, BaseAsyncResult): + task_id = task.task_id return dict( msg='Created fork of `%s` as `%s`' % (repo.repo_name, fork_name), - success=True # cannot return the repo data here since fork - # cann be done async + success=True, # cannot return the repo data here since fork + # cann be done async + task=task_id ) except Exception: log.error(traceback.format_exc()) @@ -977,22 +1694,38 @@ class ApiController(JSONRPCController): fork_name) ) - # perms handled inside - def delete_repo(self, apiuser, repoid, forks=Optional(None)): + # permission check inside + def delete_repo(self, apiuser, repoid, forks=Optional('')): """ - Deletes a given repository + Deletes a repository. This command can be executed only using api_key belonging + to user with admin rights or regular user that have admin access to repository. + When `forks` param is set it's possible to detach or delete forks of deleting + repository - :param apiuser: - :param repoid: - :param forks: detach or delete, what do do with attached forks for repo + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param forks: `detach` or `delete`, what do do with attached forks for repo + :type forks: Optional(str) + + OUTPUT:: + + id : + result: { + "msg": "Deleted repository ``", + "success": true + } + error: null + """ repo = get_repo_or_error(repoid) - if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: + if not HasPermissionAnyApi('hg.admin')(user=apiuser): # check if we have admin permission for this repo ! - if HasRepoPermissionAnyApi('repository.admin')(user=apiuser, - repo_name=repo.repo_name) is False: - raise JSONRPCError('repository `%s` does not exist' % (repoid)) + if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser, + repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) try: handle_forks = Optional.extract(forks) @@ -1004,8 +1737,8 @@ class ApiController(JSONRPCController): _forks_msg = ' ' + 'Deleted %s forks' % len(_forks) elif _forks: raise JSONRPCError( - 'Cannot delete `%s` it still contains attached forks' - % repo.repo_name + 'Cannot delete `%s` it still contains attached forks' % + (repo.repo_name,) ) RepoModel().delete(repo, forks=forks) @@ -1017,18 +1750,32 @@ class ApiController(JSONRPCController): except Exception: log.error(traceback.format_exc()) raise JSONRPCError( - 'failed to delete repository `%s`' % repo.repo_name + 'failed to delete repository `%s`' % (repo.repo_name,) ) @HasPermissionAllDecorator('hg.admin') def grant_user_permission(self, apiuser, repoid, userid, perm): """ Grant permission for user on given repository, or update existing one - if found + if found. This command can be executed only using api_key belonging to user + with admin rights. - :param repoid: + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int :param userid: - :param perm: + :param perm: (repository.(none|read|write|admin)) + :type perm: str + + OUTPUT:: + + id : + result: { + "msg" : "Granted perm: `` for user: `` in repo: ``", + "success": true + } + error: null """ repo = get_repo_or_error(repoid) user = get_user_or_error(userid) @@ -1056,19 +1803,30 @@ class ApiController(JSONRPCController): @HasPermissionAllDecorator('hg.admin') def revoke_user_permission(self, apiuser, repoid, userid): """ - Revoke permission for user on given repository + Revoke permission for user on given repository. This command can be executed + only using api_key belonging to user with admin rights. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param userid: - :param apiuser: - :param repoid: - :param userid: + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm for user: `` in repo: ``", + "success": true + } + error: null + """ repo = get_repo_or_error(repoid) user = get_user_or_error(userid) try: - RepoModel().revoke_user_permission(repo=repo, user=user) - Session().commit() return dict( msg='Revoked perm for user: `%s` in repo: `%s`' % ( @@ -1084,33 +1842,123 @@ class ApiController(JSONRPCController): ) ) - @HasPermissionAllDecorator('hg.admin') - def grant_users_group_permission(self, apiuser, repoid, usersgroupid, - perm): + # permission check inside + def grant_user_group_permission(self, apiuser, repoid, usergroupid, perm): """ Grant permission for user group on given repository, or update - existing one if found + existing one if found. This command can be executed only using + api_key belonging to user with admin rights. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param usergroupid: id of usergroup + :type usergroupid: str or int + :param perm: (repository.(none|read|write|admin)) + :type perm: str + + OUTPUT:: - :param apiuser: - :param repoid: - :param usersgroupid: - :param perm: + id : + result : { + "msg" : "Granted perm: `` for group: `` in repo: ``", + "success": true + + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user group: `` in repo ``' + } + """ repo = get_repo_or_error(repoid) perm = get_perm_or_error(perm) - users_group = get_users_group_or_error(usersgroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo ! + _perms = ('repository.admin',) + if not HasRepoPermissionAnyApi(*_perms)( + user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: - RepoModel().grant_users_group_permission(repo=repo, - group_name=users_group, - perm=perm) + RepoModel().grant_user_group_permission( + repo=repo, group_name=user_group, perm=perm) Session().commit() return dict( msg='Granted perm: `%s` for user group: `%s` in ' 'repo: `%s`' % ( - perm.permission_name, users_group.users_group_name, - repo.repo_name + perm.permission_name, user_group.users_group_name, + repo.repo_name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission for user group: `%s` in ' + 'repo: `%s`' % ( + usergroupid, repo.repo_name + ) + ) + + # permission check inside + def revoke_user_group_permission(self, apiuser, repoid, usergroupid): + """ + Revoke permission for user group on given repository. This command can be + executed only using api_key belonging to user with admin rights. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repoid: repository name or repository id + :type repoid: str or int + :param usergroupid: + + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm for group: `` in repo: ``", + "success": true + } + error: null + """ + repo = get_repo_or_error(repoid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo ! + _perms = ('repository.admin',) + if not HasRepoPermissionAnyApi(*_perms)( + user=apiuser, repo_name=repo.repo_name): + raise JSONRPCError('repository `%s` does not exist' % (repoid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) + + try: + RepoModel().revoke_user_group_permission( + repo=repo, group_name=user_group) + + Session().commit() + return dict( + msg='Revoked perm for user group: `%s` in repo: `%s`' % ( + user_group.users_group_name, repo.repo_name ), success=True ) @@ -1119,46 +1967,579 @@ class ApiController(JSONRPCController): raise JSONRPCError( 'failed to edit permission for user group: `%s` in ' 'repo: `%s`' % ( - usersgroupid, repo.repo_name + user_group.users_group_name, repo.repo_name ) ) @HasPermissionAllDecorator('hg.admin') - def revoke_users_group_permission(self, apiuser, repoid, usersgroupid): + def get_repo_group(self, apiuser, repogroupid): + """ + Returns given repo group together with permissions, and repositories + inside the group + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: id/name of repository group + :type repogroupid: str or int + """ + repo_group = get_repo_group_or_error(repogroupid) + + members = [] + for user in repo_group.repo_group_to_perm: + perm = user.permission.permission_name + user = user.user + user_data = { + 'name': user.username, + 'type': "user", + 'permission': perm + } + members.append(user_data) + + for user_group in repo_group.users_group_to_perm: + perm = user_group.permission.permission_name + user_group = user_group.users_group + user_group_data = { + 'name': user_group.users_group_name, + 'type': "user_group", + 'permission': perm + } + members.append(user_group_data) + + data = repo_group.get_api_data() + data["members"] = members + return data + + @HasPermissionAllDecorator('hg.admin') + def get_repo_groups(self, apiuser): """ - Revoke permission for user group on given repository + Returns all repository groups + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + """ + result = [] + for repo_group in RepoGroupModel().get_all(): + result.append(repo_group.get_api_data()) + return result + + @HasPermissionAllDecorator('hg.admin') + def create_repo_group(self, apiuser, group_name, description=Optional(''), + owner=Optional(OAttr('apiuser')), + parent=Optional(None), + copy_permissions=Optional(False)): + """ + Creates a repository group. This command can be executed only using + api_key belonging to user with admin rights. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param group_name: + :type group_name: + :param description: + :type description: + :param owner: + :type owner: + :param parent: + :type parent: + :param copy_permissions: + :type copy_permissions: + + OUTPUT:: + + id : + result : { + "msg": "created new repo group ``" + "repo_group": + } + error : null + + ERROR OUTPUT:: - :param apiuser: - :param repoid: - :param usersgroupid: + id : + result : null + error : { + failed to create repo group `` + } + """ - repo = get_repo_or_error(repoid) - users_group = get_users_group_or_error(usersgroupid) + if RepoGroup.get_by_group_name(group_name): + raise JSONRPCError("repo group `%s` already exist" % (group_name,)) + + if isinstance(owner, Optional): + owner = apiuser.user_id + group_description = Optional.extract(description) + parent_group = Optional.extract(parent) + if not isinstance(parent, Optional): + parent_group = get_repo_group_or_error(parent_group) + + copy_permissions = Optional.extract(copy_permissions) + try: + repo_group = RepoGroupModel().create( + group_name=group_name, + group_description=group_description, + owner=owner, + parent=parent_group, + copy_permissions=copy_permissions + ) + Session().commit() + return dict( + msg='created new repo group `%s`' % group_name, + repo_group=repo_group.get_api_data() + ) + except Exception: + + log.error(traceback.format_exc()) + raise JSONRPCError('failed to create repo group `%s`' % (group_name,)) + + @HasPermissionAllDecorator('hg.admin') + def update_repo_group(self, apiuser, repogroupid, group_name=Optional(''), + description=Optional(''), + owner=Optional(OAttr('apiuser')), + parent=Optional(None), enable_locking=Optional(False)): + repo_group = get_repo_group_or_error(repogroupid) + + updates = {} + try: + store_update(updates, group_name, 'group_name') + store_update(updates, description, 'group_description') + store_update(updates, owner, 'owner') + store_update(updates, parent, 'parent_group') + store_update(updates, enable_locking, 'enable_locking') + repo_group = RepoGroupModel().update(repo_group, updates) + Session().commit() + return dict( + msg='updated repository group ID:%s %s' % (repo_group.group_id, + repo_group.group_name), + repo_group=repo_group.get_api_data() + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to update repository group `%s`' + % (repogroupid,)) + + @HasPermissionAllDecorator('hg.admin') + def delete_repo_group(self, apiuser, repogroupid): + """ + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + + OUTPUT:: + + id : + result : { + 'msg': 'deleted repo group ID: + 'repo_group': null + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete repo group ID: " + } + + """ + repo_group = get_repo_group_or_error(repogroupid) try: - RepoModel().revoke_users_group_permission(repo=repo, - group_name=users_group) - + RepoGroupModel().delete(repo_group) Session().commit() return dict( - msg='Revoked perm for user group: `%s` in repo: `%s`' % ( - users_group.users_group_name, repo.repo_name + msg='deleted repo group ID:%s %s' % + (repo_group.group_id, repo_group.group_name), + repo_group=None + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to delete repo group ID:%s %s' % + (repo_group.group_id, repo_group.group_name) + ) + + # permission check inside + def grant_user_permission_to_repo_group(self, apiuser, repogroupid, userid, + perm, apply_to_children=Optional('none')): + """ + Grant permission for user on given repository group, or update existing + one if found. This command can be executed only using api_key belonging + to user with admin rights, or user who has admin right to given repository + group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param userid: + :param perm: (group.(none|read|write|admin)) + :type perm: str + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result: { + "msg" : "Granted perm: `` (recursive:) for user: `` in repo group: ``", + "success": true + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user: `` in repo group: ``" + } + + """ + + repo_group = get_repo_group_or_error(repogroupid) + + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser, + group_name=repo_group.group_name): + raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,)) + + user = get_user_or_error(userid) + perm = get_perm_or_error(perm, prefix='group.') + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().add_permission(repo_group=repo_group, + obj=user, + obj_type="user", + perm=perm, + recursive=apply_to_children) + Session().commit() + return dict( + msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % ( + perm.permission_name, apply_to_children, user.username, repo_group.name ), success=True ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( + 'failed to edit permission for user: `%s` in repo group: `%s`' % ( + userid, repo_group.name)) + + # permission check inside + def revoke_user_permission_from_repo_group(self, apiuser, repogroupid, userid, + apply_to_children=Optional('none')): + """ + Revoke permission for user on given repository group. This command can + be executed only using api_key belonging to user with admin rights, or + user who has admin right to given repository group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param userid: + :type userid: + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm (recursive:) for user: `` in repo group: ``", + "success": true + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user: `` in repo group: ``" + } + + """ + + repo_group = get_repo_group_or_error(repogroupid) + + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser, + group_name=repo_group.group_name): + raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,)) + + user = get_user_or_error(userid) + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().delete_permission(repo_group=repo_group, + obj=user, + obj_type="user", + recursive=apply_to_children) + + Session().commit() + return dict( + msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % ( + apply_to_children, user.username, repo_group.name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission for user: `%s` in repo group: `%s`' % ( + userid, repo_group.name)) + + # permission check inside + def grant_user_group_permission_to_repo_group( + self, apiuser, repogroupid, usergroupid, perm, + apply_to_children=Optional('none'),): + """ + Grant permission for user group on given repository group, or update + existing one if found. This command can be executed only using + api_key belonging to user with admin rights, or user who has admin + right to given repository group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param usergroupid: id of usergroup + :type usergroupid: str or int + :param perm: (group.(none|read|write|admin)) + :type perm: str + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result : { + "msg" : "Granted perm: `` (recursive:) for user group: `` in repo group: ``", + "success": true + + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user group: `` in repo group: ``" + } + + """ + repo_group = get_repo_group_or_error(repogroupid) + perm = get_perm_or_error(perm, prefix='group.') + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + _perms = ('group.admin',) + if not HasRepoGroupPermissionAnyApi(*_perms)( + user=apiuser, group_name=repo_group.group_name): + raise JSONRPCError( + 'repository group `%s` does not exist' % (repogroupid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError( + 'user group `%s` does not exist' % (usergroupid,)) + + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().add_permission(repo_group=repo_group, + obj=user_group, + obj_type="user_group", + perm=perm, + recursive=apply_to_children) + Session().commit() + return dict( + msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % ( + perm.permission_name, apply_to_children, + user_group.users_group_name, repo_group.name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( 'failed to edit permission for user group: `%s` in ' - 'repo: `%s`' % ( - users_group.users_group_name, repo.repo_name + 'repo group: `%s`' % ( + usergroupid, repo_group.name ) ) + # permission check inside + def revoke_user_group_permission_from_repo_group( + self, apiuser, repogroupid, usergroupid, + apply_to_children=Optional('none')): + """ + Revoke permission for user group on given repository. This command can be + executed only using api_key belonging to user with admin rights, or + user who has admin right to given repository group. + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param repogroupid: name or id of repository group + :type repogroupid: str or int + :param usergroupid: + :param apply_to_children: 'none', 'repos', 'groups', 'all' + :type apply_to_children: str + + OUTPUT:: + + id : + result: { + "msg" : "Revoked perm (recursive:) for user group: `` in repo group: ``", + "success": true + } + error: null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to edit permission for user group: `` in repo group: ``" + } + + + """ + repo_group = get_repo_group_or_error(repogroupid) + user_group = get_user_group_or_error(usergroupid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # check if we have admin permission for this repo group ! + _perms = ('group.admin',) + if not HasRepoGroupPermissionAnyApi(*_perms)( + user=apiuser, group_name=repo_group.group_name): + raise JSONRPCError( + 'repository group `%s` does not exist' % (repogroupid,)) + + # check if we have at least read permission for this user group ! + _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) + if not HasUserGroupPermissionAny(*_perms)( + user=apiuser, user_group_name=user_group.users_group_name): + raise JSONRPCError( + 'user group `%s` does not exist' % (usergroupid,)) + + apply_to_children = Optional.extract(apply_to_children) + + try: + RepoGroupModel().delete_permission(repo_group=repo_group, + obj=user_group, + obj_type="user_group", + recursive=apply_to_children) + Session().commit() + return dict( + msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % ( + apply_to_children, user_group.users_group_name, repo_group.name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission for user group: `%s` in repo group: `%s`' % ( + user_group.users_group_name, repo_group.name + ) + ) + + def get_gist(self, apiuser, gistid): + """ + Get given gist by id + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param gistid: id of private or public gist + :type gistid: str + """ + gist = get_gist_or_error(gistid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if gist.gist_owner != apiuser.user_id: + raise JSONRPCError('gist `%s` does not exist' % (gistid,)) + return gist.get_api_data() + + def get_gists(self, apiuser, userid=Optional(OAttr('apiuser'))): + """ + Get all gists for given user. If userid is empty returned gists + are for user who called the api + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param userid: user to get gists for + :type userid: Optional(str or int) + """ + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + # make sure normal user does not pass someone else userid, + # he is not allowed to do that + if not isinstance(userid, Optional) and userid != apiuser.user_id: + raise JSONRPCError( + 'userid is not the same as your user' + ) + + if isinstance(userid, Optional): + user_id = apiuser.user_id + else: + user_id = get_user_or_error(userid).user_id + + gists = [] + _gists = Gist().query()\ + .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\ + .filter(Gist.gist_owner == user_id)\ + .order_by(Gist.created_on.desc()) + for gist in _gists: + gists.append(gist.get_api_data()) + return gists + def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')), gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1), description=Optional('')): + """ + Creates new Gist + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param files: files to be added to gist + {'filename': {'content':'...', 'lexer': null}, + 'filename2': {'content':'...', 'lexer': null}} + :type files: dict + :param owner: gist owner, defaults to api method caller + :type owner: Optional(str or int) + :param gist_type: type of gist 'public' or 'private' + :type gist_type: Optional(str) + :param lifetime: time in minutes of gist lifetime + :type lifetime: Optional(int) + :param description: gist description + :type description: Optional(str) + + OUTPUT:: + + id : + result : { + "msg": "created new gist", + "gist": {} + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to create gist" + } + + """ try: if isinstance(owner, Optional): owner = apiuser.user_id @@ -1168,10 +2549,6 @@ class ApiController(JSONRPCController): gist_type = Optional.extract(gist_type) lifetime = Optional.extract(lifetime) - # files: { - # 'filename': {'content':'...', 'lexer': null}, - # 'filename2': {'content':'...', 'lexer': null} - #} gist = GistModel().create(description=description, owner=owner, gist_mapping=files, @@ -1185,3 +2562,54 @@ class ApiController(JSONRPCController): except Exception: log.error(traceback.format_exc()) raise JSONRPCError('failed to create gist') + + # def update_gist(self, apiuser, gistid, files, owner=Optional(OAttr('apiuser')), + # gist_type=Optional(Gist.GIST_PUBLIC), + # gist_lifetime=Optional(-1), gist_description=Optional('')): + # gist = get_gist_or_error(gistid) + # updates = {} + + # permission check inside + def delete_gist(self, apiuser, gistid): + """ + Deletes existing gist + + :param apiuser: filled automatically from apikey + :type apiuser: AuthUser + :param gistid: id of gist to delete + :type gistid: str + + OUTPUT:: + + id : + result : { + "deleted gist ID: ", + "gist": null + } + error : null + + ERROR OUTPUT:: + + id : + result : null + error : { + "failed to delete gist ID:" + } + + """ + gist = get_gist_or_error(gistid) + if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if gist.gist_owner != apiuser.user_id: + raise JSONRPCError('gist `%s` does not exist' % (gistid,)) + + try: + GistModel().delete(gist) + Session().commit() + return dict( + msg='deleted gist ID:%s' % (gist.gist_access_id,), + gist=None + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to delete gist ID:%s' + % (gist.gist_access_id,))