comparison rhodecode/controllers/api/api.py @ 3960:5293d4bbb1ea

Merged dev into stable/default/master branch
author Marcin Kuzminski <marcin@python-works.com>
date Fri, 07 Jun 2013 00:31:11 +0200
parents e3857cbb6d10 e1a0fdaecf63
children ffd45b185016
comparison
equal deleted inserted replaced
3879:51596d9ef2f8 3960:5293d4bbb1ea
23 # You should have received a copy of the GNU General Public License 23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software 24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA. 26 # MA 02110-1301, USA.
27 27
28 import time
28 import traceback 29 import traceback
29 import logging 30 import logging
30 31
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError 32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \ 33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
33 HasPermissionAllDecorator, HasPermissionAnyDecorator, \ 34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
34 HasPermissionAnyApi, HasRepoPermissionAnyApi 35 HasPermissionAnyApi, HasRepoPermissionAnyApi
35 from rhodecode.lib.utils import map_groups, repo2db_mapper 36 from rhodecode.lib.utils import map_groups, repo2db_mapper
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int 37 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int
37 from rhodecode.lib import helpers as h
38 from rhodecode.model.meta import Session 38 from rhodecode.model.meta import Session
39 from rhodecode.model.scm import ScmModel 39 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.repo import RepoModel 40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.user import UserModel 41 from rhodecode.model.user import UserModel
42 from rhodecode.model.users_group import UserGroupModel 42 from rhodecode.model.users_group import UserGroupModel
43 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\ 44 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\
44 Permission 45 Permission, User, Gist
45 from rhodecode.lib.compat import json 46 from rhodecode.lib.compat import json
47 from rhodecode.lib.exceptions import DefaultUserException
48 from rhodecode.model.gist import GistModel
46 49
47 log = logging.getLogger(__name__) 50 log = logging.getLogger(__name__)
51
52
53 def store_update(updates, attr, name):
54 """
55 Stores param in updates dict if it's not instance of Optional
56 allows easy updates of passed in params
57 """
58 if not isinstance(attr, Optional):
59 updates[name] = attr
48 60
49 61
50 class OptionalAttr(object): 62 class OptionalAttr(object):
51 """ 63 """
52 Special Optional Option that defines other attribute 64 Special Optional Option that defines other attribute
111 123
112 def get_repo_or_error(repoid): 124 def get_repo_or_error(repoid):
113 """ 125 """
114 Get repo by id or name or return JsonRPCError if not found 126 Get repo by id or name or return JsonRPCError if not found
115 127
116 :param userid: 128 :param repoid:
117 """ 129 """
118 repo = RepoModel().get_repo(repoid) 130 repo = RepoModel().get_repo(repoid)
119 if repo is None: 131 if repo is None:
120 raise JSONRPCError('repository `%s` does not exist' % (repoid)) 132 raise JSONRPCError('repository `%s` does not exist' % (repoid))
121 return repo 133 return repo
122 134
123 135
136 def get_repo_group_or_error(repogroupid):
137 """
138 Get repo group by id or name or return JsonRPCError if not found
139
140 :param repogroupid:
141 """
142 repo_group = ReposGroupModel()._get_repo_group(repogroupid)
143 if repo_group is None:
144 raise JSONRPCError(
145 'repository group `%s` does not exist' % (repogroupid,))
146 return repo_group
147
148
124 def get_users_group_or_error(usersgroupid): 149 def get_users_group_or_error(usersgroupid):
125 """ 150 """
126 Get user group by id or name or return JsonRPCError if not found 151 Get user group by id or name or return JsonRPCError if not found
127 152
128 :param userid: 153 :param userid:
210 235
211 :param apiuser: 236 :param apiuser:
212 :param repoid: 237 :param repoid:
213 """ 238 """
214 repo = get_repo_or_error(repoid) 239 repo = get_repo_or_error(repoid)
215 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: 240 if not HasPermissionAnyApi('hg.admin')(user=apiuser):
216 # check if we have admin permission for this repo ! 241 # check if we have admin permission for this repo !
217 if HasRepoPermissionAnyApi('repository.admin', 242 if HasRepoPermissionAnyApi('repository.admin',
218 'repository.write')(user=apiuser, 243 'repository.write')(user=apiuser,
219 repo_name=repo.repo_name) is False: 244 repo_name=repo.repo_name) is False:
220 raise JSONRPCError('repository `%s` does not exist' % (repoid)) 245 raise JSONRPCError('repository `%s` does not exist' % (repoid))
221 246
222 try: 247 try:
223 invalidated_keys = ScmModel().mark_for_invalidation(repo.repo_name) 248 ScmModel().mark_for_invalidation(repo.repo_name)
224 Session().commit() 249 return ('Caches of repository `%s` was invalidated' % repoid)
225 return ('Cache for repository `%s` was invalidated: '
226 'invalidated cache keys: %s' % (repoid, invalidated_keys))
227 except Exception: 250 except Exception:
228 log.error(traceback.format_exc()) 251 log.error(traceback.format_exc())
229 raise JSONRPCError( 252 raise JSONRPCError(
230 'Error occurred during cache invalidation action' 253 'Error occurred during cache invalidation action'
231 ) 254 )
232 255
256 # permission check inside
233 def lock(self, apiuser, repoid, locked=Optional(None), 257 def lock(self, apiuser, repoid, locked=Optional(None),
234 userid=Optional(OAttr('apiuser'))): 258 userid=Optional(OAttr('apiuser'))):
235 """ 259 """
236 Set locking state on particular repository by given user, if 260 Set locking state on particular repository by given user, if
237 this command is runned by non-admin account userid is set to user 261 this command is runned by non-admin account userid is set to user
264 288
265 if isinstance(locked, Optional): 289 if isinstance(locked, Optional):
266 lockobj = Repository.getlock(repo) 290 lockobj = Repository.getlock(repo)
267 291
268 if lockobj[0] is None: 292 if lockobj[0] is None:
269 return ('Repo `%s` not locked. Locked=`False`.' 293 _d = {
270 % (repo.repo_name)) 294 'repo': repo.repo_name,
295 'locked': False,
296 'locked_since': None,
297 'locked_by': None,
298 'msg': 'Repo `%s` not locked.' % repo.repo_name
299 }
300 return _d
271 else: 301 else:
272 userid, time_ = lockobj 302 userid, time_ = lockobj
273 user = get_user_or_error(userid) 303 lock_user = get_user_or_error(userid)
274 304 _d = {
275 return ('Repo `%s` locked by `%s`. Locked=`True`. ' 305 'repo': repo.repo_name,
276 'Locked since: `%s`' 306 'locked': True,
277 % (repo.repo_name, user.username, 307 'locked_since': time_,
278 json.dumps(time_to_datetime(time_)))) 308 'locked_by': lock_user.username,
279 309 'msg': ('Repo `%s` locked by `%s`. '
310 % (repo.repo_name,
311 json.dumps(time_to_datetime(time_))))
312 }
313 return _d
314
315 # force locked state through a flag
280 else: 316 else:
281 locked = str2bool(locked) 317 locked = str2bool(locked)
282 try: 318 try:
283 if locked: 319 if locked:
284 Repository.lock(repo, user.user_id) 320 lock_time = time.time()
321 Repository.lock(repo, user.user_id, lock_time)
285 else: 322 else:
323 lock_time = None
286 Repository.unlock(repo) 324 Repository.unlock(repo)
287 325 _d = {
288 return ('User `%s` set lock state for repo `%s` to `%s`' 326 'repo': repo.repo_name,
289 % (user.username, repo.repo_name, locked)) 327 'locked': locked,
328 'locked_since': lock_time,
329 'locked_by': user.username,
330 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
331 % (user.username, repo.repo_name, locked))
332 }
333 return _d
290 except Exception: 334 except Exception:
291 log.error(traceback.format_exc()) 335 log.error(traceback.format_exc())
292 raise JSONRPCError( 336 raise JSONRPCError(
293 'Error occurred locking repository `%s`' % repo.repo_name 337 'Error occurred locking repository `%s`' % repo.repo_name
294 ) 338 )
300 who is calling this method, thus returning locks for himself 344 who is calling this method, thus returning locks for himself
301 345
302 :param apiuser: 346 :param apiuser:
303 :param userid: 347 :param userid:
304 """ 348 """
305 if HasPermissionAnyApi('hg.admin')(user=apiuser): 349
306 pass 350 if not HasPermissionAnyApi('hg.admin')(user=apiuser):
307 else:
308 #make sure normal user does not pass someone else userid, 351 #make sure normal user does not pass someone else userid,
309 #he is not allowed to do that 352 #he is not allowed to do that
310 if not isinstance(userid, Optional) and userid != apiuser.user_id: 353 if not isinstance(userid, Optional) and userid != apiuser.user_id:
311 raise JSONRPCError( 354 raise JSONRPCError(
312 'userid is not the same as your user' 355 'userid is not the same as your user'
352 Get a user by username, or userid, if userid is given 395 Get a user by username, or userid, if userid is given
353 396
354 :param apiuser: 397 :param apiuser:
355 :param userid: 398 :param userid:
356 """ 399 """
357 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: 400 if not HasPermissionAnyApi('hg.admin')(user=apiuser):
358 #make sure normal user does not pass someone else userid, 401 #make sure normal user does not pass someone else userid,
359 #he is not allowed to do that 402 #he is not allowed to do that
360 if not isinstance(userid, Optional) and userid != apiuser.user_id: 403 if not isinstance(userid, Optional) and userid != apiuser.user_id:
361 raise JSONRPCError( 404 raise JSONRPCError(
362 'userid is not the same as your user' 405 'userid is not the same as your user'
377 420
378 :param apiuser: 421 :param apiuser:
379 """ 422 """
380 423
381 result = [] 424 result = []
382 for user in UserModel().get_all(): 425 users_list = User.query().order_by(User.username)\
426 .filter(User.username != User.DEFAULT_USER)\
427 .all()
428 for user in users_list:
383 result.append(user.get_api_data()) 429 result.append(user.get_api_data())
384 return result 430 return result
385 431
386 @HasPermissionAllDecorator('hg.admin') 432 @HasPermissionAllDecorator('hg.admin')
387 def create_user(self, apiuser, username, email, password, 433 def create_user(self, apiuser, username, email, password=Optional(None),
388 firstname=Optional(None), lastname=Optional(None), 434 firstname=Optional(None), lastname=Optional(None),
389 active=Optional(True), admin=Optional(False), 435 active=Optional(True), admin=Optional(False),
390 ldap_dn=Optional(None)): 436 ldap_dn=Optional(None)):
391 """ 437 """
392 Create new user 438 Create new user
477 Session().commit() 523 Session().commit()
478 return dict( 524 return dict(
479 msg='updated user ID:%s %s' % (user.user_id, user.username), 525 msg='updated user ID:%s %s' % (user.user_id, user.username),
480 user=user.get_api_data() 526 user=user.get_api_data()
481 ) 527 )
528 except DefaultUserException:
529 log.error(traceback.format_exc())
530 raise JSONRPCError('editing default user is forbidden')
482 except Exception: 531 except Exception:
483 log.error(traceback.format_exc()) 532 log.error(traceback.format_exc())
484 raise JSONRPCError('failed to update user `%s`' % userid) 533 raise JSONRPCError('failed to update user `%s`' % userid)
485 534
486 @HasPermissionAllDecorator('hg.admin') 535 @HasPermissionAllDecorator('hg.admin')
536 for users_group in UserGroupModel().get_all(): 585 for users_group in UserGroupModel().get_all():
537 result.append(users_group.get_api_data()) 586 result.append(users_group.get_api_data())
538 return result 587 return result
539 588
540 @HasPermissionAllDecorator('hg.admin') 589 @HasPermissionAllDecorator('hg.admin')
541 def create_users_group(self, apiuser, group_name, active=Optional(True)): 590 def create_users_group(self, apiuser, group_name,
591 owner=Optional(OAttr('apiuser')),
592 active=Optional(True)):
542 """ 593 """
543 Creates an new usergroup 594 Creates an new usergroup
544 595
545 :param apiuser: 596 :param apiuser:
546 :param group_name: 597 :param group_name:
598 :param owner:
547 :param active: 599 :param active:
548 """ 600 """
549 601
550 if UserGroupModel().get_by_name(group_name): 602 if UserGroupModel().get_by_name(group_name):
551 raise JSONRPCError("user group `%s` already exist" % group_name) 603 raise JSONRPCError("user group `%s` already exist" % group_name)
552 604
553 try: 605 try:
606 if isinstance(owner, Optional):
607 owner = apiuser.user_id
608
609 owner = get_user_or_error(owner)
554 active = Optional.extract(active) 610 active = Optional.extract(active)
555 ug = UserGroupModel().create(name=group_name, active=active) 611 ug = UserGroupModel().create(name=group_name,
612 owner=owner,
613 active=active)
556 Session().commit() 614 Session().commit()
557 return dict( 615 return dict(
558 msg='created new user group `%s`' % group_name, 616 msg='created new user group `%s`' % group_name,
559 users_group=ug.get_api_data() 617 users_group=ug.get_api_data()
560 ) 618 )
631 :param apiuser: 689 :param apiuser:
632 :param repoid: 690 :param repoid:
633 """ 691 """
634 repo = get_repo_or_error(repoid) 692 repo = get_repo_or_error(repoid)
635 693
636 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: 694 if not HasPermissionAnyApi('hg.admin')(user=apiuser):
637 # check if we have admin permission for this repo ! 695 # check if we have admin permission for this repo !
638 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser, 696 if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
639 repo_name=repo.repo_name) is False: 697 repo_name=repo.repo_name):
640 raise JSONRPCError('repository `%s` does not exist' % (repoid)) 698 raise JSONRPCError('repository `%s` does not exist' % (repoid))
641 699
642 members = [] 700 members = []
643 followers = [] 701 followers = []
644 for user in repo.repo_to_perm: 702 for user in repo.repo_to_perm:
663 data = repo.get_api_data() 721 data = repo.get_api_data()
664 data['members'] = members 722 data['members'] = members
665 data['followers'] = followers 723 data['followers'] = followers
666 return data 724 return data
667 725
726 # permission check inside
668 def get_repos(self, apiuser): 727 def get_repos(self, apiuser):
669 """" 728 """"
670 Get all repositories 729 Get all repositories
671 730
672 :param apiuser: 731 :param apiuser:
789 repo=repo.get_api_data() 848 repo=repo.get_api_data()
790 ) 849 )
791 except Exception: 850 except Exception:
792 log.error(traceback.format_exc()) 851 log.error(traceback.format_exc())
793 raise JSONRPCError('failed to create repository `%s`' % repo_name) 852 raise JSONRPCError('failed to create repository `%s`' % repo_name)
853
854 # permission check inside
855 def update_repo(self, apiuser, repoid, name=Optional(None),
856 owner=Optional(OAttr('apiuser')),
857 group=Optional(None),
858 description=Optional(''), private=Optional(False),
859 clone_uri=Optional(None), landing_rev=Optional('tip'),
860 enable_statistics=Optional(False),
861 enable_locking=Optional(False),
862 enable_downloads=Optional(False)):
863
864 """
865 Updates repo
866
867 :param apiuser: filled automatically from apikey
868 :type apiuser: AuthUser
869 :param repoid: repository name or repository id
870 :type repoid: str or int
871 :param name:
872 :param owner:
873 :param group:
874 :param description:
875 :param private:
876 :param clone_uri:
877 :param landing_rev:
878 :param enable_statistics:
879 :param enable_locking:
880 :param enable_downloads:
881 """
882 repo = get_repo_or_error(repoid)
883 if not HasPermissionAnyApi('hg.admin')(user=apiuser):
884 # check if we have admin permission for this repo !
885 if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
886 repo_name=repo.repo_name):
887 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
888
889 updates = {
890 # update function requires this.
891 'repo_name': repo.repo_name
892 }
893 repo_group = group
894 if not isinstance(repo_group, Optional):
895 repo_group = get_repo_group_or_error(repo_group)
896 repo_group = repo_group.group_id
897 try:
898 store_update(updates, name, 'repo_name')
899 store_update(updates, repo_group, 'repo_group')
900 store_update(updates, owner, 'user')
901 store_update(updates, description, 'repo_description')
902 store_update(updates, private, 'repo_private')
903 store_update(updates, clone_uri, 'clone_uri')
904 store_update(updates, landing_rev, 'repo_landing_rev')
905 store_update(updates, enable_statistics, 'repo_enable_statistics')
906 store_update(updates, enable_locking, 'repo_enable_locking')
907 store_update(updates, enable_downloads, 'repo_enable_downloads')
908
909 RepoModel().update(repo, **updates)
910 Session().commit()
911 return dict(
912 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
913 repository=repo.get_api_data()
914 )
915 except Exception:
916 log.error(traceback.format_exc())
917 raise JSONRPCError('failed to update repo `%s`' % repoid)
794 918
795 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') 919 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
796 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')), 920 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
797 description=Optional(''), copy_permissions=Optional(False), 921 description=Optional(''), copy_permissions=Optional(False),
798 private=Optional(False), landing_rev=Optional('tip')): 922 private=Optional(False), landing_rev=Optional('tip')):
851 raise JSONRPCError( 975 raise JSONRPCError(
852 'failed to fork repository `%s` as `%s`' % (repo_name, 976 'failed to fork repository `%s` as `%s`' % (repo_name,
853 fork_name) 977 fork_name)
854 ) 978 )
855 979
980 # perms handled inside
856 def delete_repo(self, apiuser, repoid, forks=Optional(None)): 981 def delete_repo(self, apiuser, repoid, forks=Optional(None)):
857 """ 982 """
858 Deletes a given repository 983 Deletes a given repository
859 984
860 :param apiuser: 985 :param apiuser:
872 try: 997 try:
873 handle_forks = Optional.extract(forks) 998 handle_forks = Optional.extract(forks)
874 _forks_msg = '' 999 _forks_msg = ''
875 _forks = [f for f in repo.forks] 1000 _forks = [f for f in repo.forks]
876 if handle_forks == 'detach': 1001 if handle_forks == 'detach':
877 _forks_msg = ' ' + _('Detached %s forks') % len(_forks) 1002 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
878 elif handle_forks == 'delete': 1003 elif handle_forks == 'delete':
879 _forks_msg = ' ' + _('Deleted %s forks') % len(_forks) 1004 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
880 elif _forks: 1005 elif _forks:
881 raise JSONRPCError( 1006 raise JSONRPCError(
882 'Cannot delete `%s` it still contains attached forks' 1007 'Cannot delete `%s` it still contains attached forks'
883 % repo.repo_name 1008 % repo.repo_name
884 ) 1009 )
1027 'failed to edit permission for user group: `%s` in ' 1152 'failed to edit permission for user group: `%s` in '
1028 'repo: `%s`' % ( 1153 'repo: `%s`' % (
1029 users_group.users_group_name, repo.repo_name 1154 users_group.users_group_name, repo.repo_name
1030 ) 1155 )
1031 ) 1156 )
1157
1158 def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')),
1159 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
1160 description=Optional('')):
1161
1162 try:
1163 if isinstance(owner, Optional):
1164 owner = apiuser.user_id
1165
1166 owner = get_user_or_error(owner)
1167 description = Optional.extract(description)
1168 gist_type = Optional.extract(gist_type)
1169 lifetime = Optional.extract(lifetime)
1170
1171 # files: {
1172 # 'filename': {'content':'...', 'lexer': null},
1173 # 'filename2': {'content':'...', 'lexer': null}
1174 #}
1175 gist = GistModel().create(description=description,
1176 owner=owner,
1177 gist_mapping=files,
1178 gist_type=gist_type,
1179 lifetime=lifetime)
1180 Session().commit()
1181 return dict(
1182 msg='created new gist',
1183 gist=gist.get_api_data()
1184 )
1185 except Exception:
1186 log.error(traceback.format_exc())
1187 raise JSONRPCError('failed to create gist')