api.py
1187 lines
| 40.2 KiB
| text/x-python
|
PythonLexer
r1824 | # -*- coding: utf-8 -*- | |||
""" | ||||
rhodecode.controllers.api | ||||
~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
API controller for RhodeCode | ||||
:created_on: Aug 20, 2011 | ||||
:author: marcink | ||||
:copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com> | ||||
: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 distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# 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. | ||||
r3808 | import time | |||
r1500 | import traceback | |||
import logging | ||||
r1445 | from rhodecode.controllers.api import JSONRPCController, JSONRPCError | |||
r3161 | from rhodecode.lib.auth import PasswordGenerator, AuthUser, \ | |||
HasPermissionAllDecorator, HasPermissionAnyDecorator, \ | ||||
HasPermissionAnyApi, HasRepoPermissionAnyApi | ||||
r2697 | from rhodecode.lib.utils import map_groups, repo2db_mapper | |||
r3502 | from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int | |||
r1734 | from rhodecode.model.meta import Session | |||
r1445 | from rhodecode.model.scm import ScmModel | |||
|
r1584 | from rhodecode.model.repo import RepoModel | ||
|
r1586 | from rhodecode.model.user import UserModel | ||
|
r3417 | from rhodecode.model.users_group import UserGroupModel | ||
r3959 | from rhodecode.model.repos_group import ReposGroupModel | |||
r3730 | from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\ | |||
r3840 | Permission, User, Gist | |||
r3502 | from rhodecode.lib.compat import json | |||
r3799 | from rhodecode.lib.exceptions import DefaultUserException | |||
r3840 | from rhodecode.model.gist import GistModel | |||
r1734 | ||||
|
r1593 | log = logging.getLogger(__name__) | ||
r1500 | ||||
r1445 | ||||
r3959 | def store_update(updates, attr, name): | |||
""" | ||||
Stores param in updates dict if it's not instance of Optional | ||||
allows easy updates of passed in params | ||||
""" | ||||
if not isinstance(attr, Optional): | ||||
updates[name] = attr | ||||
r3161 | class OptionalAttr(object): | |||
""" | ||||
Special Optional Option that defines other attribute | ||||
""" | ||||
def __init__(self, attr_name): | ||||
self.attr_name = attr_name | ||||
def __repr__(self): | ||||
return '<OptionalAttr:%s>' % self.attr_name | ||||
def __call__(self): | ||||
return self | ||||
#alias | ||||
OAttr = OptionalAttr | ||||
r2526 | 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 '<Optional:%s>' % 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 | ||||
:param userid: | ||||
""" | ||||
user = UserModel().get_user(userid) | ||||
if user is None: | ||||
raise JSONRPCError("user `%s` does not exist" % userid) | ||||
return user | ||||
def get_repo_or_error(repoid): | ||||
""" | ||||
Get repo by id or name or return JsonRPCError if not found | ||||
r3898 | :param repoid: | |||
r2526 | """ | |||
repo = RepoModel().get_repo(repoid) | ||||
if repo is None: | ||||
raise JSONRPCError('repository `%s` does not exist' % (repoid)) | ||||
return repo | ||||
r3959 | def get_repo_group_or_error(repogroupid): | |||
""" | ||||
Get repo group by id or name or return JsonRPCError if not found | ||||
:param repogroupid: | ||||
""" | ||||
repo_group = ReposGroupModel()._get_repo_group(repogroupid) | ||||
if repo_group is None: | ||||
raise JSONRPCError( | ||||
'repository group `%s` does not exist' % (repogroupid,)) | ||||
return repo_group | ||||
r2526 | def get_users_group_or_error(usersgroupid): | |||
""" | ||||
|
r3410 | Get user group by id or name or return JsonRPCError if not found | ||
r2526 | ||||
:param userid: | ||||
""" | ||||
|
r3417 | users_group = UserGroupModel().get_group(usersgroupid) | ||
r2526 | if users_group is None: | |||
|
r3410 | raise JSONRPCError('user group `%s` does not exist' % usersgroupid) | ||
r2526 | return users_group | |||
def get_perm_or_error(permid): | ||||
""" | ||||
Get permission by id or name or return JsonRPCError if not found | ||||
:param userid: | ||||
""" | ||||
r3730 | perm = Permission.get_by_key(permid) | |||
r2526 | if perm is None: | |||
raise JSONRPCError('permission `%s` does not exist' % (permid)) | ||||
return perm | ||||
|
r1593 | class ApiController(JSONRPCController): | ||
r1445 | """ | |||
API Controller | ||||
|
r1584 | |||
r1445 | Each method needs to have USER as argument this is then based on given | |||
API_KEY propagated as instance of user object | ||||
|
r1584 | |||
r1445 | Preferably this should be first argument also | |||
|
r1584 | |||
Each function should also **raise** JSONRPCError for any | ||||
r1445 | errors that happens | |||
|
r1584 | |||
r1445 | """ | |||
|
r1593 | @HasPermissionAllDecorator('hg.admin') | ||
r2526 | def pull(self, apiuser, repoid): | |||
r1445 | """ | |||
Dispatch pull action on given repo | ||||
|
r1584 | |||
r2526 | :param apiuser: | |||
:param repoid: | ||||
r1445 | """ | |||
r2526 | repo = get_repo_or_error(repoid) | |||
|
r1587 | |||
r1445 | try: | |||
r2526 | ScmModel().pull_changes(repo.repo_name, | |||
self.rhodecode_user.username) | ||||
return 'Pulled from `%s`' % repo.repo_name | ||||
r1445 | except Exception: | |||
r2526 | log.error(traceback.format_exc()) | |||
raise JSONRPCError( | ||||
'Unable to pull changes from `%s`' % repo.repo_name | ||||
) | ||||
r1445 | ||||
|
r1593 | @HasPermissionAllDecorator('hg.admin') | ||
r2697 | def rescan_repos(self, apiuser, remove_obsolete=Optional(False)): | |||
""" | ||||
Dispatch rescan repositories action. If remove_obsolete is set | ||||
than also delete repos that are in database but not in the filesystem. | ||||
aka "clean zombies" | ||||
:param apiuser: | ||||
:param remove_obsolete: | ||||
""" | ||||
try: | ||||
rm_obsolete = Optional.extract(remove_obsolete) | ||||
added, removed = repo2db_mapper(ScmModel().repo_scan(), | ||||
remove_obsolete=rm_obsolete) | ||||
return {'added': added, 'removed': removed} | ||||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
r2737 | 'Error occurred during rescan repositories action' | |||
) | ||||
r3235 | def invalidate_cache(self, apiuser, repoid): | |||
""" | ||||
Dispatch cache invalidation action on given repo | ||||
:param apiuser: | ||||
:param repoid: | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
r3898 | if not HasPermissionAnyApi('hg.admin')(user=apiuser): | |||
r3235 | # 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)) | ||||
try: | ||||
|
r3759 | ScmModel().mark_for_invalidation(repo.repo_name) | ||
return ('Caches of repository `%s` was invalidated' % repoid) | ||||
r3235 | except Exception: | |||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
'Error occurred during cache invalidation action' | ||||
) | ||||
r3898 | # permission check inside | |||
r3457 | def lock(self, apiuser, repoid, locked=Optional(None), | |||
userid=Optional(OAttr('apiuser'))): | ||||
r2737 | """ | |||
r3161 | 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 | ||||
r2737 | ||||
:param apiuser: | ||||
:param repoid: | ||||
:param userid: | ||||
:param locked: | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
r3161 | if HasPermissionAnyApi('hg.admin')(user=apiuser): | |||
pass | ||||
elif HasRepoPermissionAnyApi('repository.admin', | ||||
'repository.write')(user=apiuser, | ||||
repo_name=repo.repo_name): | ||||
r3168 | #make sure normal user does not pass someone else userid, | |||
r3163 | #he is not allowed to do that | |||
if not isinstance(userid, Optional) and userid != apiuser.user_id: | ||||
r3161 | raise JSONRPCError( | |||
r3163 | 'userid is not the same as your user' | |||
r3161 | ) | |||
else: | ||||
r3163 | raise JSONRPCError('repository `%s` does not exist' % (repoid)) | |||
r3161 | if isinstance(userid, Optional): | |||
userid = apiuser.user_id | ||||
r3457 | ||||
r2737 | user = get_user_or_error(userid) | |||
r3457 | ||||
if isinstance(locked, Optional): | ||||
lockobj = Repository.getlock(repo) | ||||
if lockobj[0] is None: | ||||
r3808 | _d = { | |||
'repo': repo.repo_name, | ||||
'locked': False, | ||||
'locked_since': None, | ||||
'locked_by': None, | ||||
'msg': 'Repo `%s` not locked.' % repo.repo_name | ||||
} | ||||
return _d | ||||
r2737 | else: | |||
r3457 | userid, time_ = lockobj | |||
r3808 | lock_user = get_user_or_error(userid) | |||
_d = { | ||||
'repo': repo.repo_name, | ||||
'locked': True, | ||||
'locked_since': time_, | ||||
'locked_by': lock_user.username, | ||||
'msg': ('Repo `%s` locked by `%s`. ' | ||||
% (repo.repo_name, | ||||
json.dumps(time_to_datetime(time_)))) | ||||
} | ||||
return _d | ||||
r2737 | ||||
r3808 | # force locked state through a flag | |||
r3457 | else: | |||
locked = str2bool(locked) | ||||
try: | ||||
if locked: | ||||
r3808 | lock_time = time.time() | |||
Repository.lock(repo, user.user_id, lock_time) | ||||
r3457 | else: | |||
r3808 | lock_time = None | |||
r3457 | Repository.unlock(repo) | |||
r3808 | _d = { | |||
'repo': repo.repo_name, | ||||
'locked': locked, | ||||
'locked_since': lock_time, | ||||
'locked_by': user.username, | ||||
'msg': ('User `%s` set lock state for repo `%s` to `%s`' | ||||
% (user.username, repo.repo_name, locked)) | ||||
} | ||||
return _d | ||||
r3457 | except Exception: | |||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
'Error occurred locking repository `%s`' % repo.repo_name | ||||
) | ||||
r2697 | ||||
r3502 | def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))): | |||
""" | ||||
Get all 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 | ||||
:param apiuser: | ||||
:param userid: | ||||
""" | ||||
r3898 | ||||
if not HasPermissionAnyApi('hg.admin')(user=apiuser): | ||||
r3502 | #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 | ||||
for r in Repository.getAll(): | ||||
userid, time_ = r.locked | ||||
if time_: | ||||
_api_data = r.get_api_data() | ||||
# if we use userfilter just show the locks for this user | ||||
if user: | ||||
if safe_int(userid) == user.user_id: | ||||
ret.append(_api_data) | ||||
else: | ||||
ret.append(_api_data) | ||||
return ret | ||||
r2697 | @HasPermissionAllDecorator('hg.admin') | |||
r3126 | def show_ip(self, apiuser, userid): | |||
""" | ||||
Shows IP address as seen from RhodeCode server, together with all | ||||
defined IP addresses for given user | ||||
:param apiuser: | ||||
:param userid: | ||||
""" | ||||
user = get_user_or_error(userid) | ||||
ips = UserIpMap.query().filter(UserIpMap.user == user).all() | ||||
return dict( | ||||
ip_addr_server=self.ip_addr, | ||||
user_ips=ips | ||||
) | ||||
r3162 | def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))): | |||
|
r1587 | """" | ||
r3162 | Get a user by username, or userid, if userid is given | |||
|
r1587 | |||
r1793 | :param apiuser: | |||
r2526 | :param userid: | |||
|
r1587 | """ | ||
r3898 | if not HasPermissionAnyApi('hg.admin')(user=apiuser): | |||
r3168 | #make sure normal user does not pass someone else userid, | |||
r3163 | #he is not allowed to do that | |||
if not isinstance(userid, Optional) and userid != apiuser.user_id: | ||||
r3162 | raise JSONRPCError( | |||
r3163 | 'userid is not the same as your user' | |||
r3162 | ) | |||
r3163 | ||||
if isinstance(userid, Optional): | ||||
r3162 | userid = apiuser.user_id | |||
r1445 | ||||
r2526 | user = get_user_or_error(userid) | |||
data = user.get_api_data() | ||||
data['permissions'] = AuthUser(user_id=user.user_id).permissions | ||||
return data | ||||
r1793 | ||||
@HasPermissionAllDecorator('hg.admin') | ||||
def get_users(self, apiuser): | ||||
"""" | ||||
Get all users | ||||
:param apiuser: | ||||
""" | ||||
result = [] | ||||
r3799 | users_list = User.query().order_by(User.username)\ | |||
.filter(User.username != User.DEFAULT_USER)\ | ||||
.all() | ||||
for user in users_list: | ||||
r2526 | result.append(user.get_api_data()) | |||
|
r1586 | return result | ||
|
r1593 | @HasPermissionAllDecorator('hg.admin') | ||
r3809 | def create_user(self, apiuser, username, email, password=Optional(None), | |||
r2526 | firstname=Optional(None), lastname=Optional(None), | |||
active=Optional(True), admin=Optional(False), | ||||
ldap_dn=Optional(None)): | ||||
r1500 | """ | |||
r2002 | Create new user | |||
|
r1584 | |||
r1500 | :param apiuser: | |||
:param username: | ||||
r2526 | :param email: | |||
r1500 | :param password: | |||
r2526 | :param firstname: | |||
r1500 | :param lastname: | |||
|
r1584 | :param active: | ||
:param admin: | ||||
:param ldap_dn: | ||||
r1500 | """ | |||
|
r1589 | |||
r2526 | if UserModel().get_by_username(username): | |||
raise JSONRPCError("user `%s` already exist" % username) | ||||
if UserModel().get_by_email(email, case_insensitive=True): | ||||
raise JSONRPCError("email `%s` already exist" % email) | ||||
r2008 | ||||
r2758 | if Optional.extract(ldap_dn): | |||
r2008 | # generate temporary password if ldap_dn | |||
password = PasswordGenerator().gen_password(length=8) | ||||
r1500 | try: | |||
r2365 | user = UserModel().create_or_update( | |||
r2526 | username=Optional.extract(username), | |||
password=Optional.extract(password), | ||||
email=Optional.extract(email), | ||||
firstname=Optional.extract(firstname), | ||||
lastname=Optional.extract(lastname), | ||||
active=Optional.extract(active), | ||||
admin=Optional.extract(admin), | ||||
ldap_dn=Optional.extract(ldap_dn) | ||||
r1843 | ) | |||
r2526 | Session().commit() | |||
r1843 | return dict( | |||
r2526 | msg='created new user `%s`' % username, | |||
user=user.get_api_data() | ||||
r1843 | ) | |||
r1500 | except Exception: | |||
|
r1593 | log.error(traceback.format_exc()) | ||
r2526 | raise JSONRPCError('failed to create user `%s`' % username) | |||
|
r1587 | |||
|
r1593 | @HasPermissionAllDecorator('hg.admin') | ||
r2526 | 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)): | ||||
r2002 | """ | |||
Updates given user | ||||
:param apiuser: | ||||
r2526 | :param userid: | |||
r2002 | :param username: | |||
:param email: | ||||
r2526 | :param firstname: | |||
r2002 | :param lastname: | |||
:param active: | ||||
:param admin: | ||||
:param ldap_dn: | ||||
r2526 | :param password: | |||
r2002 | """ | |||
r2526 | ||||
user = get_user_or_error(userid) | ||||
r2657 | # call function and store only updated arguments | |||
updates = {} | ||||
def store_update(attr, name): | ||||
if not isinstance(attr, Optional): | ||||
updates[name] = attr | ||||
r2002 | ||||
try: | ||||
r2526 | ||||
r2657 | 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') | ||||
user = UserModel().update_user(user, **updates) | ||||
r2526 | Session().commit() | |||
r2002 | return dict( | |||
r2507 | msg='updated user ID:%s %s' % (user.user_id, user.username), | |||
r2526 | user=user.get_api_data() | |||
r2002 | ) | |||
r3799 | except DefaultUserException: | |||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError('editing default user is forbidden') | ||||
r2002 | except Exception: | |||
log.error(traceback.format_exc()) | ||||
r2526 | raise JSONRPCError('failed to update user `%s`' % userid) | |||
r2365 | ||||
@HasPermissionAllDecorator('hg.admin') | ||||
def delete_user(self, apiuser, userid): | ||||
"""" | ||||
Deletes an user | ||||
:param apiuser: | ||||
r2526 | :param userid: | |||
r2365 | """ | |||
r2526 | user = get_user_or_error(userid) | |||
r2365 | ||||
try: | ||||
UserModel().delete(userid) | ||||
r2526 | Session().commit() | |||
r2365 | return dict( | |||
r2526 | msg='deleted user ID:%s %s' % (user.user_id, user.username), | |||
user=None | ||||
r2365 | ) | |||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
r2526 | raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id, | |||
user.username)) | ||||
r2002 | ||||
@HasPermissionAllDecorator('hg.admin') | ||||
r2526 | def get_users_group(self, apiuser, usersgroupid): | |||
|
r1587 | """" | ||
|
r3410 | Get user group by name or id | ||
|
r1587 | |||
r1793 | :param apiuser: | |||
r2526 | :param usersgroupid: | |||
|
r1587 | """ | ||
r2526 | users_group = get_users_group_or_error(usersgroupid) | |||
|
r1587 | |||
r2526 | data = users_group.get_api_data() | |||
r1445 | ||||
|
r1587 | members = [] | ||
for user in users_group.members: | ||||
user = user.user | ||||
r2526 | members.append(user.get_api_data()) | |||
data['members'] = members | ||||
return data | ||||
|
r1587 | |||
|
r1593 | @HasPermissionAllDecorator('hg.admin') | ||
def get_users_groups(self, apiuser): | ||||
|
r1584 | """" | ||
|
r3410 | Get all user groups | ||
|
r1584 | |||
r1793 | :param apiuser: | |||
|
r1584 | """ | ||
|
r1586 | |||
|
r1584 | result = [] | ||
|
r3417 | for users_group in UserGroupModel().get_all(): | ||
r2526 | result.append(users_group.get_api_data()) | |||
|
r1584 | return result | ||
|
r1593 | @HasPermissionAllDecorator('hg.admin') | ||
r3714 | def create_users_group(self, apiuser, group_name, | |||
owner=Optional(OAttr('apiuser')), | ||||
active=Optional(True)): | ||||
r1500 | """ | |||
Creates an new usergroup | ||||
|
r1584 | |||
r2526 | :param apiuser: | |||
r1843 | :param group_name: | |||
r3714 | :param owner: | |||
r1500 | :param active: | |||
""" | ||||
|
r1586 | |||
|
r3417 | if UserGroupModel().get_by_name(group_name): | ||
|
r3410 | raise JSONRPCError("user group `%s` already exist" % group_name) | ||
|
r1589 | |||
r1500 | try: | |||
r3714 | if isinstance(owner, Optional): | |||
owner = apiuser.user_id | ||||
owner = get_user_or_error(owner) | ||||
r2526 | active = Optional.extract(active) | |||
r3714 | ug = UserGroupModel().create(name=group_name, | |||
owner=owner, | ||||
active=active) | ||||
r2526 | Session().commit() | |||
return dict( | ||||
|
r3410 | msg='created new user group `%s`' % group_name, | ||
r2526 | users_group=ug.get_api_data() | |||
) | ||||
r1500 | except Exception: | |||
|
r1593 | log.error(traceback.format_exc()) | ||
r2526 | raise JSONRPCError('failed to create group `%s`' % group_name) | |||
|
r1584 | |||
|
r1593 | @HasPermissionAllDecorator('hg.admin') | ||
r2526 | def add_user_to_users_group(self, apiuser, usersgroupid, userid): | |||
|
r1584 | """" | ||
|
r3410 | Add a user to a user group | ||
|
r1584 | |||
r1793 | :param apiuser: | |||
r2526 | :param usersgroupid: | |||
:param userid: | ||||
|
r1584 | """ | ||
r2526 | user = get_user_or_error(userid) | |||
users_group = get_users_group_or_error(usersgroupid) | ||||
|
r1584 | |||
|
r1586 | try: | ||
|
r3417 | ugm = UserGroupModel().add_user_to_group(users_group, user) | ||
r1989 | success = True if ugm != True else False | |||
|
r3410 | msg = 'added member `%s` to user group `%s`' % ( | ||
r2526 | user.username, users_group.users_group_name | |||
) | ||||
r1989 | msg = msg if success else 'User is already in that group' | |||
r2526 | Session().commit() | |||
r1989 | ||||
return dict( | ||||
success=success, | ||||
msg=msg | ||||
) | ||||
|
r1587 | except Exception: | ||
|
r1593 | log.error(traceback.format_exc()) | ||
r2526 | raise JSONRPCError( | |||
|
r3410 | 'failed to add member to user group `%s`' % ( | ||
r2526 | users_group.users_group_name | |||
) | ||||
) | ||||
r1989 | ||||
@HasPermissionAllDecorator('hg.admin') | ||||
r2526 | def remove_user_from_users_group(self, apiuser, usersgroupid, userid): | |||
r1989 | """ | |||
Remove user from a group | ||||
r2526 | :param apiuser: | |||
:param usersgroupid: | ||||
:param userid: | ||||
r1989 | """ | |||
r2526 | user = get_user_or_error(userid) | |||
users_group = get_users_group_or_error(usersgroupid) | ||||
r1989 | ||||
try: | ||||
|
r3417 | success = UserGroupModel().remove_user_from_group(users_group, | ||
r2526 | user) | |||
|
r3410 | msg = 'removed member `%s` from user group `%s`' % ( | ||
r2526 | user.username, users_group.users_group_name | |||
) | ||||
r1989 | msg = msg if success else "User wasn't in group" | |||
r2526 | Session().commit() | |||
r1989 | return dict(success=success, msg=msg) | |||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
r2526 | raise JSONRPCError( | |||
|
r3410 | 'failed to remove member from user group `%s`' % ( | ||
r2526 | users_group.users_group_name | |||
) | ||||
) | ||||
|
r1587 | |||
r2010 | def get_repo(self, apiuser, repoid): | |||
|
r1587 | """" | ||
Get repository by name | ||||
r1793 | :param apiuser: | |||
r2526 | :param repoid: | |||
|
r1587 | """ | ||
r2526 | repo = get_repo_or_error(repoid) | |||
|
r1584 | |||
r3898 | if not HasPermissionAnyApi('hg.admin')(user=apiuser): | |||
r3163 | # check if we have admin permission for this repo ! | |||
r3898 | if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser, | |||
repo_name=repo.repo_name): | ||||
r3163 | raise JSONRPCError('repository `%s` does not exist' % (repoid)) | |||
|
r1587 | members = [] | ||
|
r3195 | followers = [] | ||
|
r1587 | for user in repo.repo_to_perm: | ||
perm = user.permission.permission_name | ||||
user = user.user | ||||
r2526 | user_data = user.get_api_data() | |||
user_data['type'] = "user" | ||||
user_data['permission'] = perm | ||||
members.append(user_data) | ||||
|
r1587 | for users_group in repo.users_group_to_perm: | ||
perm = users_group.permission.permission_name | ||||
users_group = users_group.users_group | ||||
r2526 | users_group_data = users_group.get_api_data() | |||
users_group_data['type'] = "users_group" | ||||
users_group_data['permission'] = perm | ||||
members.append(users_group_data) | ||||
|
r1584 | |||
|
r3195 | for user in repo.followers: | ||
followers.append(user.user.get_api_data()) | ||||
r2526 | data = repo.get_api_data() | |||
data['members'] = members | ||||
|
r3195 | data['followers'] = followers | ||
r2526 | return data | |||
|
r1587 | |||
r3898 | # permission check inside | |||
|
r1593 | def get_repos(self, apiuser): | ||
|
r1586 | """" | ||
|
r1587 | Get all repositories | ||
|
r1586 | |||
r1793 | :param apiuser: | |||
|
r1586 | """ | ||
r3163 | result = [] | |||
if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | ||||
repos = RepoModel().get_all_user_repos(user=apiuser) | ||||
else: | ||||
repos = RepoModel().get_all() | ||||
|
r1587 | |||
r3163 | for repo in repos: | |||
r2526 | result.append(repo.get_api_data()) | |||
|
r1586 | return result | ||
r3161 | @HasPermissionAllDecorator('hg.admin') | |||
r2526 | def get_repo_nodes(self, apiuser, repoid, revision, root_path, | |||
r1810 | ret_type='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 | ||||
:param apiuser: | ||||
r2526 | :param repoid: name or id of repository | |||
r1810 | :param revision: revision for which listing should be done | |||
:param root_path: path from which start displaying | ||||
:param ret_type: return type 'all|files|dirs' nodes | ||||
""" | ||||
r2526 | repo = get_repo_or_error(repoid) | |||
r1810 | try: | |||
r2526 | _d, _f = ScmModel().get_nodes(repo, revision, root_path, | |||
r1810 | flat=False) | |||
_map = { | ||||
'all': _d + _f, | ||||
'files': _f, | ||||
'dirs': _d, | ||||
} | ||||
return _map[ret_type] | ||||
except KeyError: | ||||
raise JSONRPCError('ret_type must be one of %s' % _map.keys()) | ||||
r2526 | except Exception: | |||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
'failed to get repo: `%s` nodes' % repo.repo_name | ||||
) | ||||
r1810 | ||||
|
r1593 | @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') | ||
r3163 | def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')), | |||
repo_type=Optional('hg'), | ||||
r2526 | description=Optional(''), private=Optional(False), | |||
r3115 | clone_uri=Optional(None), landing_rev=Optional('tip'), | |||
enable_statistics=Optional(False), | ||||
enable_locking=Optional(False), | ||||
enable_downloads=Optional(False)): | ||||
|
r1584 | """ | ||
r2006 | Create repository, if clone_url is given it makes a remote clone | |||
r3163 | if repo_name is within a group name the groups will be created | |||
r2526 | automatically if they aren't present | |||
|
r1584 | |||
r1793 | :param apiuser: | |||
r1843 | :param repo_name: | |||
r2526 | :param onwer: | |||
:param repo_type: | ||||
r1793 | :param description: | |||
:param private: | ||||
r2006 | :param clone_uri: | |||
r2526 | :param landing_rev: | |||
|
r1584 | """ | ||
r3163 | if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | |||
if not isinstance(owner, Optional): | ||||
#forbid setting owner for non-admins | ||||
raise JSONRPCError( | ||||
'Only RhodeCode admin can specify `owner` param' | ||||
) | ||||
if isinstance(owner, Optional): | ||||
owner = apiuser.user_id | ||||
r2526 | owner = get_user_or_error(owner) | |||
if RepoModel().get_by_repo_name(repo_name): | ||||
raise JSONRPCError("repo `%s` already exist" % repo_name) | ||||
r3115 | defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) | |||
if isinstance(private, Optional): | ||||
private = defs.get('repo_private') or Optional.extract(private) | ||||
if isinstance(repo_type, Optional): | ||||
repo_type = defs.get('repo_type') | ||||
if isinstance(enable_statistics, Optional): | ||||
enable_statistics = defs.get('repo_enable_statistics') | ||||
if isinstance(enable_locking, Optional): | ||||
enable_locking = defs.get('repo_enable_locking') | ||||
if isinstance(enable_downloads, Optional): | ||||
enable_downloads = defs.get('repo_enable_downloads') | ||||
r2526 | clone_uri = Optional.extract(clone_uri) | |||
description = Optional.extract(description) | ||||
landing_rev = Optional.extract(landing_rev) | ||||
|
r1584 | |||
|
r1586 | try: | ||
r2526 | # create structure of groups and return the last group | |||
r2120 | group = map_groups(repo_name) | |||
|
r1589 | |||
r2526 | repo = RepoModel().create_repo( | |||
repo_name=repo_name, | ||||
repo_type=repo_type, | ||||
description=description, | ||||
owner=owner, | ||||
private=private, | ||||
clone_uri=clone_uri, | ||||
repos_group=group, | ||||
landing_rev=landing_rev, | ||||
r3115 | enable_statistics=enable_statistics, | |||
enable_downloads=enable_downloads, | ||||
enable_locking=enable_locking | ||||
r1793 | ) | |||
r2526 | ||||
Session().commit() | ||||
r1843 | return dict( | |||
r2526 | msg="Created new repository `%s`" % (repo.repo_name), | |||
repo=repo.get_api_data() | ||||
r1843 | ) | |||
|
r1584 | except Exception: | ||
|
r1593 | log.error(traceback.format_exc()) | ||
r2526 | raise JSONRPCError('failed to create repository `%s`' % repo_name) | |||
r3959 | # 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'), | ||||
enable_statistics=Optional(False), | ||||
enable_locking=Optional(False), | ||||
enable_downloads=Optional(False)): | ||||
""" | ||||
Updates repo | ||||
:param apiuser: filled automatically from apikey | ||||
:type apiuser: AuthUser | ||||
:param repoid: repository name or repository id | ||||
:type repoid: str or int | ||||
:param name: | ||||
:param owner: | ||||
:param group: | ||||
:param description: | ||||
:param private: | ||||
:param clone_uri: | ||||
:param landing_rev: | ||||
:param enable_statistics: | ||||
:param enable_locking: | ||||
:param enable_downloads: | ||||
""" | ||||
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,)) | ||||
updates = { | ||||
# update function requires this. | ||||
'repo_name': repo.repo_name | ||||
} | ||||
repo_group = group | ||||
if not isinstance(repo_group, Optional): | ||||
repo_group = get_repo_group_or_error(repo_group) | ||||
repo_group = repo_group.group_id | ||||
try: | ||||
store_update(updates, name, 'repo_name') | ||||
store_update(updates, repo_group, 'repo_group') | ||||
store_update(updates, owner, 'user') | ||||
store_update(updates, description, 'repo_description') | ||||
store_update(updates, private, 'repo_private') | ||||
store_update(updates, clone_uri, 'clone_uri') | ||||
store_update(updates, landing_rev, 'repo_landing_rev') | ||||
store_update(updates, enable_statistics, 'repo_enable_statistics') | ||||
store_update(updates, enable_locking, 'repo_enable_locking') | ||||
store_update(updates, enable_downloads, 'repo_enable_downloads') | ||||
RepoModel().update(repo, **updates) | ||||
Session().commit() | ||||
return dict( | ||||
msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name), | ||||
repository=repo.get_api_data() | ||||
) | ||||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError('failed to update repo `%s`' % repoid) | ||||
r3163 | @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') | |||
def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')), | ||||
r2653 | description=Optional(''), copy_permissions=Optional(False), | |||
private=Optional(False), landing_rev=Optional('tip')): | ||||
repo = get_repo_or_error(repoid) | ||||
repo_name = repo.repo_name | ||||
_repo = RepoModel().get_by_repo_name(fork_name) | ||||
if _repo: | ||||
type_ = 'fork' if _repo.fork else 'repo' | ||||
raise JSONRPCError("%s `%s` already exist" % (type_, fork_name)) | ||||
r3163 | if HasPermissionAnyApi('hg.admin')(user=apiuser): | |||
pass | ||||
elif HasRepoPermissionAnyApi('repository.admin', | ||||
'repository.write', | ||||
'repository.read')(user=apiuser, | ||||
repo_name=repo.repo_name): | ||||
if not isinstance(owner, Optional): | ||||
#forbid setting owner for non-admins | ||||
raise JSONRPCError( | ||||
'Only RhodeCode admin can specify `owner` param' | ||||
) | ||||
else: | ||||
raise JSONRPCError('repository `%s` does not exist' % (repoid)) | ||||
if isinstance(owner, Optional): | ||||
owner = apiuser.user_id | ||||
owner = get_user_or_error(owner) | ||||
r2653 | try: | |||
# create structure of groups and return the last group | ||||
group = map_groups(fork_name) | ||||
form_data = dict( | ||||
repo_name=fork_name, | ||||
repo_name_full=fork_name, | ||||
repo_group=group, | ||||
repo_type=repo.repo_type, | ||||
description=Optional.extract(description), | ||||
private=Optional.extract(private), | ||||
copy_permissions=Optional.extract(copy_permissions), | ||||
landing_rev=Optional.extract(landing_rev), | ||||
update_after_clone=False, | ||||
fork_parent_id=repo.repo_id, | ||||
) | ||||
RepoModel().create_fork(form_data, cur_user=owner) | ||||
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 | ||||
) | ||||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
'failed to fork repository `%s` as `%s`' % (repo_name, | ||||
fork_name) | ||||
) | ||||
|
r1584 | |||
r3840 | # perms handled inside | |||
r3641 | def delete_repo(self, apiuser, repoid, forks=Optional(None)): | |||
r2003 | """ | |||
Deletes a given repository | ||||
r2526 | :param apiuser: | |||
:param repoid: | ||||
r3641 | :param forks: detach or delete, what do do with attached forks for repo | |||
r2003 | """ | |||
r2526 | repo = get_repo_or_error(repoid) | |||
r3163 | if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | |||
# check if we have admin permission for this repo ! | ||||
if HasRepoPermissionAnyApi('repository.admin')(user=apiuser, | ||||
repo_name=repo.repo_name) is False: | ||||
r3641 | raise JSONRPCError('repository `%s` does not exist' % (repoid)) | |||
r3163 | ||||
r2003 | try: | |||
r3641 | handle_forks = Optional.extract(forks) | |||
_forks_msg = '' | ||||
_forks = [f for f in repo.forks] | ||||
if handle_forks == 'detach': | ||||
r3884 | _forks_msg = ' ' + 'Detached %s forks' % len(_forks) | |||
r3641 | elif handle_forks == 'delete': | |||
r3884 | _forks_msg = ' ' + 'Deleted %s forks' % len(_forks) | |||
r3641 | elif _forks: | |||
raise JSONRPCError( | ||||
'Cannot delete `%s` it still contains attached forks' | ||||
% repo.repo_name | ||||
) | ||||
RepoModel().delete(repo, forks=forks) | ||||
r2526 | Session().commit() | |||
r2003 | return dict( | |||
r3641 | msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg), | |||
r2526 | success=True | |||
r2003 | ) | |||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
r2526 | raise JSONRPCError( | |||
'failed to delete repository `%s`' % repo.repo_name | ||||
) | ||||
r2003 | ||||
r3161 | @HasPermissionAllDecorator('hg.admin') | |||
r2526 | def grant_user_permission(self, apiuser, repoid, userid, perm): | |||
|
r1586 | """ | ||
r1982 | Grant permission for user on given repository, or update existing one | |||
if found | ||||
|
r1584 | |||
r2526 | :param repoid: | |||
:param userid: | ||||
r1793 | :param perm: | |||
|
r1586 | """ | ||
r2526 | repo = get_repo_or_error(repoid) | |||
user = get_user_or_error(userid) | ||||
perm = get_perm_or_error(perm) | ||||
|
r1586 | |||
try: | ||||
r1982 | RepoModel().grant_user_permission(repo=repo, user=user, perm=perm) | |||
r2526 | Session().commit() | |||
r1793 | return dict( | |||
r2526 | msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % ( | |||
perm.permission_name, user.username, repo.repo_name | ||||
), | ||||
success=True | ||||
r1793 | ) | |||
|
r1586 | except Exception: | ||
|
r1593 | log.error(traceback.format_exc()) | ||
r1793 | raise JSONRPCError( | |||
r2526 | 'failed to edit permission for user: `%s` in repo: `%s`' % ( | |||
userid, repoid | ||||
r1793 | ) | |||
) | ||||
r3161 | @HasPermissionAllDecorator('hg.admin') | |||
r2526 | def revoke_user_permission(self, apiuser, repoid, userid): | |||
r1982 | """ | |||
Revoke permission for user on given repository | ||||
r2526 | :param apiuser: | |||
:param repoid: | ||||
:param userid: | ||||
r1793 | """ | |||
r1982 | ||||
r2526 | repo = get_repo_or_error(repoid) | |||
user = get_user_or_error(userid) | ||||
r1982 | try: | |||
r2526 | ||||
RepoModel().revoke_user_permission(repo=repo, user=user) | ||||
r1982 | ||||
r2526 | Session().commit() | |||
r1982 | return dict( | |||
r2526 | msg='Revoked perm for user: `%s` in repo: `%s`' % ( | |||
user.username, repo.repo_name | ||||
), | ||||
success=True | ||||
r1982 | ) | |||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
r2526 | 'failed to edit permission for user: `%s` in repo: `%s`' % ( | |||
userid, repoid | ||||
r1982 | ) | |||
) | ||||
r3161 | @HasPermissionAllDecorator('hg.admin') | |||
r2526 | def grant_users_group_permission(self, apiuser, repoid, usersgroupid, | |||
perm): | ||||
r1982 | """ | |||
|
r3410 | Grant permission for user group on given repository, or update | ||
r1982 | existing one if found | |||
r2526 | :param apiuser: | |||
:param repoid: | ||||
:param usersgroupid: | ||||
r1793 | :param perm: | |||
""" | ||||
r2526 | repo = get_repo_or_error(repoid) | |||
perm = get_perm_or_error(perm) | ||||
users_group = get_users_group_or_error(usersgroupid) | ||||
r1793 | ||||
try: | ||||
r2526 | RepoModel().grant_users_group_permission(repo=repo, | |||
group_name=users_group, | ||||
r1982 | perm=perm) | |||
r2526 | Session().commit() | |||
r1793 | return dict( | |||
|
r3410 | msg='Granted perm: `%s` for user group: `%s` in ' | ||
r2526 | 'repo: `%s`' % ( | |||
perm.permission_name, users_group.users_group_name, | ||||
repo.repo_name | ||||
), | ||||
success=True | ||||
) | ||||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
|
r3410 | 'failed to edit permission for user group: `%s` in ' | ||
r2526 | 'repo: `%s`' % ( | |||
usersgroupid, repo.repo_name | ||||
r1793 | ) | |||
) | ||||
r2526 | ||||
r3161 | @HasPermissionAllDecorator('hg.admin') | |||
r2526 | def revoke_users_group_permission(self, apiuser, repoid, usersgroupid): | |||
""" | ||||
|
r3410 | Revoke permission for user group on given repository | ||
r2526 | ||||
:param apiuser: | ||||
:param repoid: | ||||
:param usersgroupid: | ||||
""" | ||||
repo = get_repo_or_error(repoid) | ||||
users_group = get_users_group_or_error(usersgroupid) | ||||
try: | ||||
RepoModel().revoke_users_group_permission(repo=repo, | ||||
group_name=users_group) | ||||
Session().commit() | ||||
return dict( | ||||
|
r3410 | msg='Revoked perm for user group: `%s` in repo: `%s`' % ( | ||
r2526 | users_group.users_group_name, repo.repo_name | |||
), | ||||
success=True | ||||
) | ||||
r1793 | except Exception: | |||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError( | ||||
|
r3410 | 'failed to edit permission for user group: `%s` in ' | ||
r2526 | 'repo: `%s`' % ( | |||
users_group.users_group_name, repo.repo_name | ||||
r1793 | ) | |||
) | ||||
r3840 | ||||
def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')), | ||||
r3958 | gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1), | |||
description=Optional('')): | ||||
r3840 | ||||
try: | ||||
if isinstance(owner, Optional): | ||||
owner = apiuser.user_id | ||||
owner = get_user_or_error(owner) | ||||
r3958 | description = Optional.extract(description) | |||
r3840 | gist_type = Optional.extract(gist_type) | |||
r3958 | lifetime = Optional.extract(lifetime) | |||
r3840 | ||||
# files: { | ||||
# 'filename': {'content':'...', 'lexer': null}, | ||||
# 'filename2': {'content':'...', 'lexer': null} | ||||
#} | ||||
gist = GistModel().create(description=description, | ||||
owner=owner, | ||||
gist_mapping=files, | ||||
gist_type=gist_type, | ||||
r3958 | lifetime=lifetime) | |||
r3840 | Session().commit() | |||
return dict( | ||||
msg='created new gist', | ||||
r3958 | gist=gist.get_api_data() | |||
r3840 | ) | |||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
raise JSONRPCError('failed to create gist') | ||||