Mercurial > kallithea
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') |