##// END OF EJS Templates
audit-logs: use specific web/api calls....
marcink -
r1806:99a19fa8 default
parent child Browse files
Show More
@@ -1,2063 +1,2062 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
36 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.model.changeset_status import ChangesetStatusModel
38 from rhodecode.model.changeset_status import ChangesetStatusModel
39 from rhodecode.model.comment import CommentsModel
39 from rhodecode.model.comment import CommentsModel
40 from rhodecode.model.db import (
40 from rhodecode.model.db import (
41 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
41 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
42 ChangesetComment)
42 ChangesetComment)
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.scm import ScmModel, RepoList
44 from rhodecode.model.scm import ScmModel, RepoList
45 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
45 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
46 from rhodecode.model import validation_schema
46 from rhodecode.model import validation_schema
47 from rhodecode.model.validation_schema.schemas import repo_schema
47 from rhodecode.model.validation_schema.schemas import repo_schema
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 @jsonrpc_method()
52 @jsonrpc_method()
53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
54 """
54 """
55 Gets an existing repository by its name or repository_id.
55 Gets an existing repository by its name or repository_id.
56
56
57 The members section so the output returns users groups or users
57 The members section so the output returns users groups or users
58 associated with that repository.
58 associated with that repository.
59
59
60 This command can only be run using an |authtoken| with admin rights,
60 This command can only be run using an |authtoken| with admin rights,
61 or users with at least read rights to the |repo|.
61 or users with at least read rights to the |repo|.
62
62
63 :param apiuser: This is filled automatically from the |authtoken|.
63 :param apiuser: This is filled automatically from the |authtoken|.
64 :type apiuser: AuthUser
64 :type apiuser: AuthUser
65 :param repoid: The repository name or repository id.
65 :param repoid: The repository name or repository id.
66 :type repoid: str or int
66 :type repoid: str or int
67 :param cache: use the cached value for last changeset
67 :param cache: use the cached value for last changeset
68 :type: cache: Optional(bool)
68 :type: cache: Optional(bool)
69
69
70 Example output:
70 Example output:
71
71
72 .. code-block:: bash
72 .. code-block:: bash
73
73
74 {
74 {
75 "error": null,
75 "error": null,
76 "id": <repo_id>,
76 "id": <repo_id>,
77 "result": {
77 "result": {
78 "clone_uri": null,
78 "clone_uri": null,
79 "created_on": "timestamp",
79 "created_on": "timestamp",
80 "description": "repo description",
80 "description": "repo description",
81 "enable_downloads": false,
81 "enable_downloads": false,
82 "enable_locking": false,
82 "enable_locking": false,
83 "enable_statistics": false,
83 "enable_statistics": false,
84 "followers": [
84 "followers": [
85 {
85 {
86 "active": true,
86 "active": true,
87 "admin": false,
87 "admin": false,
88 "api_key": "****************************************",
88 "api_key": "****************************************",
89 "api_keys": [
89 "api_keys": [
90 "****************************************"
90 "****************************************"
91 ],
91 ],
92 "email": "user@example.com",
92 "email": "user@example.com",
93 "emails": [
93 "emails": [
94 "user@example.com"
94 "user@example.com"
95 ],
95 ],
96 "extern_name": "rhodecode",
96 "extern_name": "rhodecode",
97 "extern_type": "rhodecode",
97 "extern_type": "rhodecode",
98 "firstname": "username",
98 "firstname": "username",
99 "ip_addresses": [],
99 "ip_addresses": [],
100 "language": null,
100 "language": null,
101 "last_login": "2015-09-16T17:16:35.854",
101 "last_login": "2015-09-16T17:16:35.854",
102 "lastname": "surname",
102 "lastname": "surname",
103 "user_id": <user_id>,
103 "user_id": <user_id>,
104 "username": "name"
104 "username": "name"
105 }
105 }
106 ],
106 ],
107 "fork_of": "parent-repo",
107 "fork_of": "parent-repo",
108 "landing_rev": [
108 "landing_rev": [
109 "rev",
109 "rev",
110 "tip"
110 "tip"
111 ],
111 ],
112 "last_changeset": {
112 "last_changeset": {
113 "author": "User <user@example.com>",
113 "author": "User <user@example.com>",
114 "branch": "default",
114 "branch": "default",
115 "date": "timestamp",
115 "date": "timestamp",
116 "message": "last commit message",
116 "message": "last commit message",
117 "parents": [
117 "parents": [
118 {
118 {
119 "raw_id": "commit-id"
119 "raw_id": "commit-id"
120 }
120 }
121 ],
121 ],
122 "raw_id": "commit-id",
122 "raw_id": "commit-id",
123 "revision": <revision number>,
123 "revision": <revision number>,
124 "short_id": "short id"
124 "short_id": "short id"
125 },
125 },
126 "lock_reason": null,
126 "lock_reason": null,
127 "locked_by": null,
127 "locked_by": null,
128 "locked_date": null,
128 "locked_date": null,
129 "members": [
129 "members": [
130 {
130 {
131 "name": "super-admin-name",
131 "name": "super-admin-name",
132 "origin": "super-admin",
132 "origin": "super-admin",
133 "permission": "repository.admin",
133 "permission": "repository.admin",
134 "type": "user"
134 "type": "user"
135 },
135 },
136 {
136 {
137 "name": "owner-name",
137 "name": "owner-name",
138 "origin": "owner",
138 "origin": "owner",
139 "permission": "repository.admin",
139 "permission": "repository.admin",
140 "type": "user"
140 "type": "user"
141 },
141 },
142 {
142 {
143 "name": "user-group-name",
143 "name": "user-group-name",
144 "origin": "permission",
144 "origin": "permission",
145 "permission": "repository.write",
145 "permission": "repository.write",
146 "type": "user_group"
146 "type": "user_group"
147 }
147 }
148 ],
148 ],
149 "owner": "owner-name",
149 "owner": "owner-name",
150 "permissions": [
150 "permissions": [
151 {
151 {
152 "name": "super-admin-name",
152 "name": "super-admin-name",
153 "origin": "super-admin",
153 "origin": "super-admin",
154 "permission": "repository.admin",
154 "permission": "repository.admin",
155 "type": "user"
155 "type": "user"
156 },
156 },
157 {
157 {
158 "name": "owner-name",
158 "name": "owner-name",
159 "origin": "owner",
159 "origin": "owner",
160 "permission": "repository.admin",
160 "permission": "repository.admin",
161 "type": "user"
161 "type": "user"
162 },
162 },
163 {
163 {
164 "name": "user-group-name",
164 "name": "user-group-name",
165 "origin": "permission",
165 "origin": "permission",
166 "permission": "repository.write",
166 "permission": "repository.write",
167 "type": "user_group"
167 "type": "user_group"
168 }
168 }
169 ],
169 ],
170 "private": true,
170 "private": true,
171 "repo_id": 676,
171 "repo_id": 676,
172 "repo_name": "user-group/repo-name",
172 "repo_name": "user-group/repo-name",
173 "repo_type": "hg"
173 "repo_type": "hg"
174 }
174 }
175 }
175 }
176 """
176 """
177
177
178 repo = get_repo_or_error(repoid)
178 repo = get_repo_or_error(repoid)
179 cache = Optional.extract(cache)
179 cache = Optional.extract(cache)
180
180
181 include_secrets = False
181 include_secrets = False
182 if has_superadmin_permission(apiuser):
182 if has_superadmin_permission(apiuser):
183 include_secrets = True
183 include_secrets = True
184 else:
184 else:
185 # check if we have at least read permission for this repo !
185 # check if we have at least read permission for this repo !
186 _perms = (
186 _perms = (
187 'repository.admin', 'repository.write', 'repository.read',)
187 'repository.admin', 'repository.write', 'repository.read',)
188 validate_repo_permissions(apiuser, repoid, repo, _perms)
188 validate_repo_permissions(apiuser, repoid, repo, _perms)
189
189
190 permissions = []
190 permissions = []
191 for _user in repo.permissions():
191 for _user in repo.permissions():
192 user_data = {
192 user_data = {
193 'name': _user.username,
193 'name': _user.username,
194 'permission': _user.permission,
194 'permission': _user.permission,
195 'origin': get_origin(_user),
195 'origin': get_origin(_user),
196 'type': "user",
196 'type': "user",
197 }
197 }
198 permissions.append(user_data)
198 permissions.append(user_data)
199
199
200 for _user_group in repo.permission_user_groups():
200 for _user_group in repo.permission_user_groups():
201 user_group_data = {
201 user_group_data = {
202 'name': _user_group.users_group_name,
202 'name': _user_group.users_group_name,
203 'permission': _user_group.permission,
203 'permission': _user_group.permission,
204 'origin': get_origin(_user_group),
204 'origin': get_origin(_user_group),
205 'type': "user_group",
205 'type': "user_group",
206 }
206 }
207 permissions.append(user_group_data)
207 permissions.append(user_group_data)
208
208
209 following_users = [
209 following_users = [
210 user.user.get_api_data(include_secrets=include_secrets)
210 user.user.get_api_data(include_secrets=include_secrets)
211 for user in repo.followers]
211 for user in repo.followers]
212
212
213 if not cache:
213 if not cache:
214 repo.update_commit_cache()
214 repo.update_commit_cache()
215 data = repo.get_api_data(include_secrets=include_secrets)
215 data = repo.get_api_data(include_secrets=include_secrets)
216 data['members'] = permissions # TODO: this should be deprecated soon
216 data['members'] = permissions # TODO: this should be deprecated soon
217 data['permissions'] = permissions
217 data['permissions'] = permissions
218 data['followers'] = following_users
218 data['followers'] = following_users
219 return data
219 return data
220
220
221
221
222 @jsonrpc_method()
222 @jsonrpc_method()
223 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
223 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
224 """
224 """
225 Lists all existing repositories.
225 Lists all existing repositories.
226
226
227 This command can only be run using an |authtoken| with admin rights,
227 This command can only be run using an |authtoken| with admin rights,
228 or users with at least read rights to |repos|.
228 or users with at least read rights to |repos|.
229
229
230 :param apiuser: This is filled automatically from the |authtoken|.
230 :param apiuser: This is filled automatically from the |authtoken|.
231 :type apiuser: AuthUser
231 :type apiuser: AuthUser
232 :param root: specify root repository group to fetch repositories.
232 :param root: specify root repository group to fetch repositories.
233 filters the returned repositories to be members of given root group.
233 filters the returned repositories to be members of given root group.
234 :type root: Optional(None)
234 :type root: Optional(None)
235 :param traverse: traverse given root into subrepositories. With this flag
235 :param traverse: traverse given root into subrepositories. With this flag
236 set to False, it will only return top-level repositories from `root`.
236 set to False, it will only return top-level repositories from `root`.
237 if root is empty it will return just top-level repositories.
237 if root is empty it will return just top-level repositories.
238 :type traverse: Optional(True)
238 :type traverse: Optional(True)
239
239
240
240
241 Example output:
241 Example output:
242
242
243 .. code-block:: bash
243 .. code-block:: bash
244
244
245 id : <id_given_in_input>
245 id : <id_given_in_input>
246 result: [
246 result: [
247 {
247 {
248 "repo_id" : "<repo_id>",
248 "repo_id" : "<repo_id>",
249 "repo_name" : "<reponame>"
249 "repo_name" : "<reponame>"
250 "repo_type" : "<repo_type>",
250 "repo_type" : "<repo_type>",
251 "clone_uri" : "<clone_uri>",
251 "clone_uri" : "<clone_uri>",
252 "private": : "<bool>",
252 "private": : "<bool>",
253 "created_on" : "<datetimecreated>",
253 "created_on" : "<datetimecreated>",
254 "description" : "<description>",
254 "description" : "<description>",
255 "landing_rev": "<landing_rev>",
255 "landing_rev": "<landing_rev>",
256 "owner": "<repo_owner>",
256 "owner": "<repo_owner>",
257 "fork_of": "<name_of_fork_parent>",
257 "fork_of": "<name_of_fork_parent>",
258 "enable_downloads": "<bool>",
258 "enable_downloads": "<bool>",
259 "enable_locking": "<bool>",
259 "enable_locking": "<bool>",
260 "enable_statistics": "<bool>",
260 "enable_statistics": "<bool>",
261 },
261 },
262 ...
262 ...
263 ]
263 ]
264 error: null
264 error: null
265 """
265 """
266
266
267 include_secrets = has_superadmin_permission(apiuser)
267 include_secrets = has_superadmin_permission(apiuser)
268 _perms = ('repository.read', 'repository.write', 'repository.admin',)
268 _perms = ('repository.read', 'repository.write', 'repository.admin',)
269 extras = {'user': apiuser}
269 extras = {'user': apiuser}
270
270
271 root = Optional.extract(root)
271 root = Optional.extract(root)
272 traverse = Optional.extract(traverse, binary=True)
272 traverse = Optional.extract(traverse, binary=True)
273
273
274 if root:
274 if root:
275 # verify parent existance, if it's empty return an error
275 # verify parent existance, if it's empty return an error
276 parent = RepoGroup.get_by_group_name(root)
276 parent = RepoGroup.get_by_group_name(root)
277 if not parent:
277 if not parent:
278 raise JSONRPCError(
278 raise JSONRPCError(
279 'Root repository group `{}` does not exist'.format(root))
279 'Root repository group `{}` does not exist'.format(root))
280
280
281 if traverse:
281 if traverse:
282 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
282 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
283 else:
283 else:
284 repos = RepoModel().get_repos_for_root(root=parent)
284 repos = RepoModel().get_repos_for_root(root=parent)
285 else:
285 else:
286 if traverse:
286 if traverse:
287 repos = RepoModel().get_all()
287 repos = RepoModel().get_all()
288 else:
288 else:
289 # return just top-level
289 # return just top-level
290 repos = RepoModel().get_repos_for_root(root=None)
290 repos = RepoModel().get_repos_for_root(root=None)
291
291
292 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
292 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
293 return [repo.get_api_data(include_secrets=include_secrets)
293 return [repo.get_api_data(include_secrets=include_secrets)
294 for repo in repo_list]
294 for repo in repo_list]
295
295
296
296
297 @jsonrpc_method()
297 @jsonrpc_method()
298 def get_repo_changeset(request, apiuser, repoid, revision,
298 def get_repo_changeset(request, apiuser, repoid, revision,
299 details=Optional('basic')):
299 details=Optional('basic')):
300 """
300 """
301 Returns information about a changeset.
301 Returns information about a changeset.
302
302
303 Additionally parameters define the amount of details returned by
303 Additionally parameters define the amount of details returned by
304 this function.
304 this function.
305
305
306 This command can only be run using an |authtoken| with admin rights,
306 This command can only be run using an |authtoken| with admin rights,
307 or users with at least read rights to the |repo|.
307 or users with at least read rights to the |repo|.
308
308
309 :param apiuser: This is filled automatically from the |authtoken|.
309 :param apiuser: This is filled automatically from the |authtoken|.
310 :type apiuser: AuthUser
310 :type apiuser: AuthUser
311 :param repoid: The repository name or repository id
311 :param repoid: The repository name or repository id
312 :type repoid: str or int
312 :type repoid: str or int
313 :param revision: revision for which listing should be done
313 :param revision: revision for which listing should be done
314 :type revision: str
314 :type revision: str
315 :param details: details can be 'basic|extended|full' full gives diff
315 :param details: details can be 'basic|extended|full' full gives diff
316 info details like the diff itself, and number of changed files etc.
316 info details like the diff itself, and number of changed files etc.
317 :type details: Optional(str)
317 :type details: Optional(str)
318
318
319 """
319 """
320 repo = get_repo_or_error(repoid)
320 repo = get_repo_or_error(repoid)
321 if not has_superadmin_permission(apiuser):
321 if not has_superadmin_permission(apiuser):
322 _perms = (
322 _perms = (
323 'repository.admin', 'repository.write', 'repository.read',)
323 'repository.admin', 'repository.write', 'repository.read',)
324 validate_repo_permissions(apiuser, repoid, repo, _perms)
324 validate_repo_permissions(apiuser, repoid, repo, _perms)
325
325
326 changes_details = Optional.extract(details)
326 changes_details = Optional.extract(details)
327 _changes_details_types = ['basic', 'extended', 'full']
327 _changes_details_types = ['basic', 'extended', 'full']
328 if changes_details not in _changes_details_types:
328 if changes_details not in _changes_details_types:
329 raise JSONRPCError(
329 raise JSONRPCError(
330 'ret_type must be one of %s' % (
330 'ret_type must be one of %s' % (
331 ','.join(_changes_details_types)))
331 ','.join(_changes_details_types)))
332
332
333 pre_load = ['author', 'branch', 'date', 'message', 'parents',
333 pre_load = ['author', 'branch', 'date', 'message', 'parents',
334 'status', '_commit', '_file_paths']
334 'status', '_commit', '_file_paths']
335
335
336 try:
336 try:
337 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
337 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
338 except TypeError as e:
338 except TypeError as e:
339 raise JSONRPCError(e.message)
339 raise JSONRPCError(e.message)
340 _cs_json = cs.__json__()
340 _cs_json = cs.__json__()
341 _cs_json['diff'] = build_commit_data(cs, changes_details)
341 _cs_json['diff'] = build_commit_data(cs, changes_details)
342 if changes_details == 'full':
342 if changes_details == 'full':
343 _cs_json['refs'] = {
343 _cs_json['refs'] = {
344 'branches': [cs.branch],
344 'branches': [cs.branch],
345 'bookmarks': getattr(cs, 'bookmarks', []),
345 'bookmarks': getattr(cs, 'bookmarks', []),
346 'tags': cs.tags
346 'tags': cs.tags
347 }
347 }
348 return _cs_json
348 return _cs_json
349
349
350
350
351 @jsonrpc_method()
351 @jsonrpc_method()
352 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
352 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
353 details=Optional('basic')):
353 details=Optional('basic')):
354 """
354 """
355 Returns a set of commits limited by the number starting
355 Returns a set of commits limited by the number starting
356 from the `start_rev` option.
356 from the `start_rev` option.
357
357
358 Additional parameters define the amount of details returned by this
358 Additional parameters define the amount of details returned by this
359 function.
359 function.
360
360
361 This command can only be run using an |authtoken| with admin rights,
361 This command can only be run using an |authtoken| with admin rights,
362 or users with at least read rights to |repos|.
362 or users with at least read rights to |repos|.
363
363
364 :param apiuser: This is filled automatically from the |authtoken|.
364 :param apiuser: This is filled automatically from the |authtoken|.
365 :type apiuser: AuthUser
365 :type apiuser: AuthUser
366 :param repoid: The repository name or repository ID.
366 :param repoid: The repository name or repository ID.
367 :type repoid: str or int
367 :type repoid: str or int
368 :param start_rev: The starting revision from where to get changesets.
368 :param start_rev: The starting revision from where to get changesets.
369 :type start_rev: str
369 :type start_rev: str
370 :param limit: Limit the number of commits to this amount
370 :param limit: Limit the number of commits to this amount
371 :type limit: str or int
371 :type limit: str or int
372 :param details: Set the level of detail returned. Valid option are:
372 :param details: Set the level of detail returned. Valid option are:
373 ``basic``, ``extended`` and ``full``.
373 ``basic``, ``extended`` and ``full``.
374 :type details: Optional(str)
374 :type details: Optional(str)
375
375
376 .. note::
376 .. note::
377
377
378 Setting the parameter `details` to the value ``full`` is extensive
378 Setting the parameter `details` to the value ``full`` is extensive
379 and returns details like the diff itself, and the number
379 and returns details like the diff itself, and the number
380 of changed files.
380 of changed files.
381
381
382 """
382 """
383 repo = get_repo_or_error(repoid)
383 repo = get_repo_or_error(repoid)
384 if not has_superadmin_permission(apiuser):
384 if not has_superadmin_permission(apiuser):
385 _perms = (
385 _perms = (
386 'repository.admin', 'repository.write', 'repository.read',)
386 'repository.admin', 'repository.write', 'repository.read',)
387 validate_repo_permissions(apiuser, repoid, repo, _perms)
387 validate_repo_permissions(apiuser, repoid, repo, _perms)
388
388
389 changes_details = Optional.extract(details)
389 changes_details = Optional.extract(details)
390 _changes_details_types = ['basic', 'extended', 'full']
390 _changes_details_types = ['basic', 'extended', 'full']
391 if changes_details not in _changes_details_types:
391 if changes_details not in _changes_details_types:
392 raise JSONRPCError(
392 raise JSONRPCError(
393 'ret_type must be one of %s' % (
393 'ret_type must be one of %s' % (
394 ','.join(_changes_details_types)))
394 ','.join(_changes_details_types)))
395
395
396 limit = int(limit)
396 limit = int(limit)
397 pre_load = ['author', 'branch', 'date', 'message', 'parents',
397 pre_load = ['author', 'branch', 'date', 'message', 'parents',
398 'status', '_commit', '_file_paths']
398 'status', '_commit', '_file_paths']
399
399
400 vcs_repo = repo.scm_instance()
400 vcs_repo = repo.scm_instance()
401 # SVN needs a special case to distinguish its index and commit id
401 # SVN needs a special case to distinguish its index and commit id
402 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
402 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
403 start_rev = vcs_repo.commit_ids[0]
403 start_rev = vcs_repo.commit_ids[0]
404
404
405 try:
405 try:
406 commits = vcs_repo.get_commits(
406 commits = vcs_repo.get_commits(
407 start_id=start_rev, pre_load=pre_load)
407 start_id=start_rev, pre_load=pre_load)
408 except TypeError as e:
408 except TypeError as e:
409 raise JSONRPCError(e.message)
409 raise JSONRPCError(e.message)
410 except Exception:
410 except Exception:
411 log.exception('Fetching of commits failed')
411 log.exception('Fetching of commits failed')
412 raise JSONRPCError('Error occurred during commit fetching')
412 raise JSONRPCError('Error occurred during commit fetching')
413
413
414 ret = []
414 ret = []
415 for cnt, commit in enumerate(commits):
415 for cnt, commit in enumerate(commits):
416 if cnt >= limit != -1:
416 if cnt >= limit != -1:
417 break
417 break
418 _cs_json = commit.__json__()
418 _cs_json = commit.__json__()
419 _cs_json['diff'] = build_commit_data(commit, changes_details)
419 _cs_json['diff'] = build_commit_data(commit, changes_details)
420 if changes_details == 'full':
420 if changes_details == 'full':
421 _cs_json['refs'] = {
421 _cs_json['refs'] = {
422 'branches': [commit.branch],
422 'branches': [commit.branch],
423 'bookmarks': getattr(commit, 'bookmarks', []),
423 'bookmarks': getattr(commit, 'bookmarks', []),
424 'tags': commit.tags
424 'tags': commit.tags
425 }
425 }
426 ret.append(_cs_json)
426 ret.append(_cs_json)
427 return ret
427 return ret
428
428
429
429
430 @jsonrpc_method()
430 @jsonrpc_method()
431 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
431 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
432 ret_type=Optional('all'), details=Optional('basic'),
432 ret_type=Optional('all'), details=Optional('basic'),
433 max_file_bytes=Optional(None)):
433 max_file_bytes=Optional(None)):
434 """
434 """
435 Returns a list of nodes and children in a flat list for a given
435 Returns a list of nodes and children in a flat list for a given
436 path at given revision.
436 path at given revision.
437
437
438 It's possible to specify ret_type to show only `files` or `dirs`.
438 It's possible to specify ret_type to show only `files` or `dirs`.
439
439
440 This command can only be run using an |authtoken| with admin rights,
440 This command can only be run using an |authtoken| with admin rights,
441 or users with at least read rights to |repos|.
441 or users with at least read rights to |repos|.
442
442
443 :param apiuser: This is filled automatically from the |authtoken|.
443 :param apiuser: This is filled automatically from the |authtoken|.
444 :type apiuser: AuthUser
444 :type apiuser: AuthUser
445 :param repoid: The repository name or repository ID.
445 :param repoid: The repository name or repository ID.
446 :type repoid: str or int
446 :type repoid: str or int
447 :param revision: The revision for which listing should be done.
447 :param revision: The revision for which listing should be done.
448 :type revision: str
448 :type revision: str
449 :param root_path: The path from which to start displaying.
449 :param root_path: The path from which to start displaying.
450 :type root_path: str
450 :type root_path: str
451 :param ret_type: Set the return type. Valid options are
451 :param ret_type: Set the return type. Valid options are
452 ``all`` (default), ``files`` and ``dirs``.
452 ``all`` (default), ``files`` and ``dirs``.
453 :type ret_type: Optional(str)
453 :type ret_type: Optional(str)
454 :param details: Returns extended information about nodes, such as
454 :param details: Returns extended information about nodes, such as
455 md5, binary, and or content. The valid options are ``basic`` and
455 md5, binary, and or content. The valid options are ``basic`` and
456 ``full``.
456 ``full``.
457 :type details: Optional(str)
457 :type details: Optional(str)
458 :param max_file_bytes: Only return file content under this file size bytes
458 :param max_file_bytes: Only return file content under this file size bytes
459 :type details: Optional(int)
459 :type details: Optional(int)
460
460
461 Example output:
461 Example output:
462
462
463 .. code-block:: bash
463 .. code-block:: bash
464
464
465 id : <id_given_in_input>
465 id : <id_given_in_input>
466 result: [
466 result: [
467 {
467 {
468 "name" : "<name>"
468 "name" : "<name>"
469 "type" : "<type>",
469 "type" : "<type>",
470 "binary": "<true|false>" (only in extended mode)
470 "binary": "<true|false>" (only in extended mode)
471 "md5" : "<md5 of file content>" (only in extended mode)
471 "md5" : "<md5 of file content>" (only in extended mode)
472 },
472 },
473 ...
473 ...
474 ]
474 ]
475 error: null
475 error: null
476 """
476 """
477
477
478 repo = get_repo_or_error(repoid)
478 repo = get_repo_or_error(repoid)
479 if not has_superadmin_permission(apiuser):
479 if not has_superadmin_permission(apiuser):
480 _perms = (
480 _perms = (
481 'repository.admin', 'repository.write', 'repository.read',)
481 'repository.admin', 'repository.write', 'repository.read',)
482 validate_repo_permissions(apiuser, repoid, repo, _perms)
482 validate_repo_permissions(apiuser, repoid, repo, _perms)
483
483
484 ret_type = Optional.extract(ret_type)
484 ret_type = Optional.extract(ret_type)
485 details = Optional.extract(details)
485 details = Optional.extract(details)
486 _extended_types = ['basic', 'full']
486 _extended_types = ['basic', 'full']
487 if details not in _extended_types:
487 if details not in _extended_types:
488 raise JSONRPCError(
488 raise JSONRPCError(
489 'ret_type must be one of %s' % (','.join(_extended_types)))
489 'ret_type must be one of %s' % (','.join(_extended_types)))
490 extended_info = False
490 extended_info = False
491 content = False
491 content = False
492 if details == 'basic':
492 if details == 'basic':
493 extended_info = True
493 extended_info = True
494
494
495 if details == 'full':
495 if details == 'full':
496 extended_info = content = True
496 extended_info = content = True
497
497
498 _map = {}
498 _map = {}
499 try:
499 try:
500 # check if repo is not empty by any chance, skip quicker if it is.
500 # check if repo is not empty by any chance, skip quicker if it is.
501 _scm = repo.scm_instance()
501 _scm = repo.scm_instance()
502 if _scm.is_empty():
502 if _scm.is_empty():
503 return []
503 return []
504
504
505 _d, _f = ScmModel().get_nodes(
505 _d, _f = ScmModel().get_nodes(
506 repo, revision, root_path, flat=False,
506 repo, revision, root_path, flat=False,
507 extended_info=extended_info, content=content,
507 extended_info=extended_info, content=content,
508 max_file_bytes=max_file_bytes)
508 max_file_bytes=max_file_bytes)
509 _map = {
509 _map = {
510 'all': _d + _f,
510 'all': _d + _f,
511 'files': _f,
511 'files': _f,
512 'dirs': _d,
512 'dirs': _d,
513 }
513 }
514 return _map[ret_type]
514 return _map[ret_type]
515 except KeyError:
515 except KeyError:
516 raise JSONRPCError(
516 raise JSONRPCError(
517 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
517 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
518 except Exception:
518 except Exception:
519 log.exception("Exception occurred while trying to get repo nodes")
519 log.exception("Exception occurred while trying to get repo nodes")
520 raise JSONRPCError(
520 raise JSONRPCError(
521 'failed to get repo: `%s` nodes' % repo.repo_name
521 'failed to get repo: `%s` nodes' % repo.repo_name
522 )
522 )
523
523
524
524
525 @jsonrpc_method()
525 @jsonrpc_method()
526 def get_repo_refs(request, apiuser, repoid):
526 def get_repo_refs(request, apiuser, repoid):
527 """
527 """
528 Returns a dictionary of current references. It returns
528 Returns a dictionary of current references. It returns
529 bookmarks, branches, closed_branches, and tags for given repository
529 bookmarks, branches, closed_branches, and tags for given repository
530
530
531 It's possible to specify ret_type to show only `files` or `dirs`.
531 It's possible to specify ret_type to show only `files` or `dirs`.
532
532
533 This command can only be run using an |authtoken| with admin rights,
533 This command can only be run using an |authtoken| with admin rights,
534 or users with at least read rights to |repos|.
534 or users with at least read rights to |repos|.
535
535
536 :param apiuser: This is filled automatically from the |authtoken|.
536 :param apiuser: This is filled automatically from the |authtoken|.
537 :type apiuser: AuthUser
537 :type apiuser: AuthUser
538 :param repoid: The repository name or repository ID.
538 :param repoid: The repository name or repository ID.
539 :type repoid: str or int
539 :type repoid: str or int
540
540
541 Example output:
541 Example output:
542
542
543 .. code-block:: bash
543 .. code-block:: bash
544
544
545 id : <id_given_in_input>
545 id : <id_given_in_input>
546 "result": {
546 "result": {
547 "bookmarks": {
547 "bookmarks": {
548 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
548 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
549 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
549 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
550 },
550 },
551 "branches": {
551 "branches": {
552 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
552 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
553 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
553 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
554 },
554 },
555 "branches_closed": {},
555 "branches_closed": {},
556 "tags": {
556 "tags": {
557 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
557 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
558 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
558 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
559 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
559 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
560 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
560 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
561 }
561 }
562 }
562 }
563 error: null
563 error: null
564 """
564 """
565
565
566 repo = get_repo_or_error(repoid)
566 repo = get_repo_or_error(repoid)
567 if not has_superadmin_permission(apiuser):
567 if not has_superadmin_permission(apiuser):
568 _perms = ('repository.admin', 'repository.write', 'repository.read',)
568 _perms = ('repository.admin', 'repository.write', 'repository.read',)
569 validate_repo_permissions(apiuser, repoid, repo, _perms)
569 validate_repo_permissions(apiuser, repoid, repo, _perms)
570
570
571 try:
571 try:
572 # check if repo is not empty by any chance, skip quicker if it is.
572 # check if repo is not empty by any chance, skip quicker if it is.
573 vcs_instance = repo.scm_instance()
573 vcs_instance = repo.scm_instance()
574 refs = vcs_instance.refs()
574 refs = vcs_instance.refs()
575 return refs
575 return refs
576 except Exception:
576 except Exception:
577 log.exception("Exception occurred while trying to get repo refs")
577 log.exception("Exception occurred while trying to get repo refs")
578 raise JSONRPCError(
578 raise JSONRPCError(
579 'failed to get repo: `%s` references' % repo.repo_name
579 'failed to get repo: `%s` references' % repo.repo_name
580 )
580 )
581
581
582
582
583 @jsonrpc_method()
583 @jsonrpc_method()
584 def create_repo(
584 def create_repo(
585 request, apiuser, repo_name, repo_type,
585 request, apiuser, repo_name, repo_type,
586 owner=Optional(OAttr('apiuser')),
586 owner=Optional(OAttr('apiuser')),
587 description=Optional(''),
587 description=Optional(''),
588 private=Optional(False),
588 private=Optional(False),
589 clone_uri=Optional(None),
589 clone_uri=Optional(None),
590 landing_rev=Optional('rev:tip'),
590 landing_rev=Optional('rev:tip'),
591 enable_statistics=Optional(False),
591 enable_statistics=Optional(False),
592 enable_locking=Optional(False),
592 enable_locking=Optional(False),
593 enable_downloads=Optional(False),
593 enable_downloads=Optional(False),
594 copy_permissions=Optional(False)):
594 copy_permissions=Optional(False)):
595 """
595 """
596 Creates a repository.
596 Creates a repository.
597
597
598 * If the repository name contains "/", repository will be created inside
598 * If the repository name contains "/", repository will be created inside
599 a repository group or nested repository groups
599 a repository group or nested repository groups
600
600
601 For example "foo/bar/repo1" will create |repo| called "repo1" inside
601 For example "foo/bar/repo1" will create |repo| called "repo1" inside
602 group "foo/bar". You have to have permissions to access and write to
602 group "foo/bar". You have to have permissions to access and write to
603 the last repository group ("bar" in this example)
603 the last repository group ("bar" in this example)
604
604
605 This command can only be run using an |authtoken| with at least
605 This command can only be run using an |authtoken| with at least
606 permissions to create repositories, or write permissions to
606 permissions to create repositories, or write permissions to
607 parent repository groups.
607 parent repository groups.
608
608
609 :param apiuser: This is filled automatically from the |authtoken|.
609 :param apiuser: This is filled automatically from the |authtoken|.
610 :type apiuser: AuthUser
610 :type apiuser: AuthUser
611 :param repo_name: Set the repository name.
611 :param repo_name: Set the repository name.
612 :type repo_name: str
612 :type repo_name: str
613 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
613 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
614 :type repo_type: str
614 :type repo_type: str
615 :param owner: user_id or username
615 :param owner: user_id or username
616 :type owner: Optional(str)
616 :type owner: Optional(str)
617 :param description: Set the repository description.
617 :param description: Set the repository description.
618 :type description: Optional(str)
618 :type description: Optional(str)
619 :param private: set repository as private
619 :param private: set repository as private
620 :type private: bool
620 :type private: bool
621 :param clone_uri: set clone_uri
621 :param clone_uri: set clone_uri
622 :type clone_uri: str
622 :type clone_uri: str
623 :param landing_rev: <rev_type>:<rev>
623 :param landing_rev: <rev_type>:<rev>
624 :type landing_rev: str
624 :type landing_rev: str
625 :param enable_locking:
625 :param enable_locking:
626 :type enable_locking: bool
626 :type enable_locking: bool
627 :param enable_downloads:
627 :param enable_downloads:
628 :type enable_downloads: bool
628 :type enable_downloads: bool
629 :param enable_statistics:
629 :param enable_statistics:
630 :type enable_statistics: bool
630 :type enable_statistics: bool
631 :param copy_permissions: Copy permission from group in which the
631 :param copy_permissions: Copy permission from group in which the
632 repository is being created.
632 repository is being created.
633 :type copy_permissions: bool
633 :type copy_permissions: bool
634
634
635
635
636 Example output:
636 Example output:
637
637
638 .. code-block:: bash
638 .. code-block:: bash
639
639
640 id : <id_given_in_input>
640 id : <id_given_in_input>
641 result: {
641 result: {
642 "msg": "Created new repository `<reponame>`",
642 "msg": "Created new repository `<reponame>`",
643 "success": true,
643 "success": true,
644 "task": "<celery task id or None if done sync>"
644 "task": "<celery task id or None if done sync>"
645 }
645 }
646 error: null
646 error: null
647
647
648
648
649 Example error output:
649 Example error output:
650
650
651 .. code-block:: bash
651 .. code-block:: bash
652
652
653 id : <id_given_in_input>
653 id : <id_given_in_input>
654 result : null
654 result : null
655 error : {
655 error : {
656 'failed to create repository `<repo_name>`'
656 'failed to create repository `<repo_name>`'
657 }
657 }
658
658
659 """
659 """
660
660
661 owner = validate_set_owner_permissions(apiuser, owner)
661 owner = validate_set_owner_permissions(apiuser, owner)
662
662
663 description = Optional.extract(description)
663 description = Optional.extract(description)
664 copy_permissions = Optional.extract(copy_permissions)
664 copy_permissions = Optional.extract(copy_permissions)
665 clone_uri = Optional.extract(clone_uri)
665 clone_uri = Optional.extract(clone_uri)
666 landing_commit_ref = Optional.extract(landing_rev)
666 landing_commit_ref = Optional.extract(landing_rev)
667
667
668 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
668 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
669 if isinstance(private, Optional):
669 if isinstance(private, Optional):
670 private = defs.get('repo_private') or Optional.extract(private)
670 private = defs.get('repo_private') or Optional.extract(private)
671 if isinstance(repo_type, Optional):
671 if isinstance(repo_type, Optional):
672 repo_type = defs.get('repo_type')
672 repo_type = defs.get('repo_type')
673 if isinstance(enable_statistics, Optional):
673 if isinstance(enable_statistics, Optional):
674 enable_statistics = defs.get('repo_enable_statistics')
674 enable_statistics = defs.get('repo_enable_statistics')
675 if isinstance(enable_locking, Optional):
675 if isinstance(enable_locking, Optional):
676 enable_locking = defs.get('repo_enable_locking')
676 enable_locking = defs.get('repo_enable_locking')
677 if isinstance(enable_downloads, Optional):
677 if isinstance(enable_downloads, Optional):
678 enable_downloads = defs.get('repo_enable_downloads')
678 enable_downloads = defs.get('repo_enable_downloads')
679
679
680 schema = repo_schema.RepoSchema().bind(
680 schema = repo_schema.RepoSchema().bind(
681 repo_type_options=rhodecode.BACKENDS.keys(),
681 repo_type_options=rhodecode.BACKENDS.keys(),
682 # user caller
682 # user caller
683 user=apiuser)
683 user=apiuser)
684
684
685 try:
685 try:
686 schema_data = schema.deserialize(dict(
686 schema_data = schema.deserialize(dict(
687 repo_name=repo_name,
687 repo_name=repo_name,
688 repo_type=repo_type,
688 repo_type=repo_type,
689 repo_owner=owner.username,
689 repo_owner=owner.username,
690 repo_description=description,
690 repo_description=description,
691 repo_landing_commit_ref=landing_commit_ref,
691 repo_landing_commit_ref=landing_commit_ref,
692 repo_clone_uri=clone_uri,
692 repo_clone_uri=clone_uri,
693 repo_private=private,
693 repo_private=private,
694 repo_copy_permissions=copy_permissions,
694 repo_copy_permissions=copy_permissions,
695 repo_enable_statistics=enable_statistics,
695 repo_enable_statistics=enable_statistics,
696 repo_enable_downloads=enable_downloads,
696 repo_enable_downloads=enable_downloads,
697 repo_enable_locking=enable_locking))
697 repo_enable_locking=enable_locking))
698 except validation_schema.Invalid as err:
698 except validation_schema.Invalid as err:
699 raise JSONRPCValidationError(colander_exc=err)
699 raise JSONRPCValidationError(colander_exc=err)
700
700
701 try:
701 try:
702 data = {
702 data = {
703 'owner': owner,
703 'owner': owner,
704 'repo_name': schema_data['repo_group']['repo_name_without_group'],
704 'repo_name': schema_data['repo_group']['repo_name_without_group'],
705 'repo_name_full': schema_data['repo_name'],
705 'repo_name_full': schema_data['repo_name'],
706 'repo_group': schema_data['repo_group']['repo_group_id'],
706 'repo_group': schema_data['repo_group']['repo_group_id'],
707 'repo_type': schema_data['repo_type'],
707 'repo_type': schema_data['repo_type'],
708 'repo_description': schema_data['repo_description'],
708 'repo_description': schema_data['repo_description'],
709 'repo_private': schema_data['repo_private'],
709 'repo_private': schema_data['repo_private'],
710 'clone_uri': schema_data['repo_clone_uri'],
710 'clone_uri': schema_data['repo_clone_uri'],
711 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
711 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
712 'enable_statistics': schema_data['repo_enable_statistics'],
712 'enable_statistics': schema_data['repo_enable_statistics'],
713 'enable_locking': schema_data['repo_enable_locking'],
713 'enable_locking': schema_data['repo_enable_locking'],
714 'enable_downloads': schema_data['repo_enable_downloads'],
714 'enable_downloads': schema_data['repo_enable_downloads'],
715 'repo_copy_permissions': schema_data['repo_copy_permissions'],
715 'repo_copy_permissions': schema_data['repo_copy_permissions'],
716 }
716 }
717
717
718 task = RepoModel().create(form_data=data, cur_user=owner)
718 task = RepoModel().create(form_data=data, cur_user=owner)
719 from celery.result import BaseAsyncResult
719 from celery.result import BaseAsyncResult
720 task_id = None
720 task_id = None
721 if isinstance(task, BaseAsyncResult):
721 if isinstance(task, BaseAsyncResult):
722 task_id = task.task_id
722 task_id = task.task_id
723 # no commit, it's done in RepoModel, or async via celery
723 # no commit, it's done in RepoModel, or async via celery
724 return {
724 return {
725 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
725 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
726 'success': True, # cannot return the repo data here since fork
726 'success': True, # cannot return the repo data here since fork
727 # can be done async
727 # can be done async
728 'task': task_id
728 'task': task_id
729 }
729 }
730 except Exception:
730 except Exception:
731 log.exception(
731 log.exception(
732 u"Exception while trying to create the repository %s",
732 u"Exception while trying to create the repository %s",
733 schema_data['repo_name'])
733 schema_data['repo_name'])
734 raise JSONRPCError(
734 raise JSONRPCError(
735 'failed to create repository `%s`' % (schema_data['repo_name'],))
735 'failed to create repository `%s`' % (schema_data['repo_name'],))
736
736
737
737
738 @jsonrpc_method()
738 @jsonrpc_method()
739 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
739 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
740 description=Optional('')):
740 description=Optional('')):
741 """
741 """
742 Adds an extra field to a repository.
742 Adds an extra field to a repository.
743
743
744 This command can only be run using an |authtoken| with at least
744 This command can only be run using an |authtoken| with at least
745 write permissions to the |repo|.
745 write permissions to the |repo|.
746
746
747 :param apiuser: This is filled automatically from the |authtoken|.
747 :param apiuser: This is filled automatically from the |authtoken|.
748 :type apiuser: AuthUser
748 :type apiuser: AuthUser
749 :param repoid: Set the repository name or repository id.
749 :param repoid: Set the repository name or repository id.
750 :type repoid: str or int
750 :type repoid: str or int
751 :param key: Create a unique field key for this repository.
751 :param key: Create a unique field key for this repository.
752 :type key: str
752 :type key: str
753 :param label:
753 :param label:
754 :type label: Optional(str)
754 :type label: Optional(str)
755 :param description:
755 :param description:
756 :type description: Optional(str)
756 :type description: Optional(str)
757 """
757 """
758 repo = get_repo_or_error(repoid)
758 repo = get_repo_or_error(repoid)
759 if not has_superadmin_permission(apiuser):
759 if not has_superadmin_permission(apiuser):
760 _perms = ('repository.admin',)
760 _perms = ('repository.admin',)
761 validate_repo_permissions(apiuser, repoid, repo, _perms)
761 validate_repo_permissions(apiuser, repoid, repo, _perms)
762
762
763 label = Optional.extract(label) or key
763 label = Optional.extract(label) or key
764 description = Optional.extract(description)
764 description = Optional.extract(description)
765
765
766 field = RepositoryField.get_by_key_name(key, repo)
766 field = RepositoryField.get_by_key_name(key, repo)
767 if field:
767 if field:
768 raise JSONRPCError('Field with key '
768 raise JSONRPCError('Field with key '
769 '`%s` exists for repo `%s`' % (key, repoid))
769 '`%s` exists for repo `%s`' % (key, repoid))
770
770
771 try:
771 try:
772 RepoModel().add_repo_field(repo, key, field_label=label,
772 RepoModel().add_repo_field(repo, key, field_label=label,
773 field_desc=description)
773 field_desc=description)
774 Session().commit()
774 Session().commit()
775 return {
775 return {
776 'msg': "Added new repository field `%s`" % (key,),
776 'msg': "Added new repository field `%s`" % (key,),
777 'success': True,
777 'success': True,
778 }
778 }
779 except Exception:
779 except Exception:
780 log.exception("Exception occurred while trying to add field to repo")
780 log.exception("Exception occurred while trying to add field to repo")
781 raise JSONRPCError(
781 raise JSONRPCError(
782 'failed to create new field for repository `%s`' % (repoid,))
782 'failed to create new field for repository `%s`' % (repoid,))
783
783
784
784
785 @jsonrpc_method()
785 @jsonrpc_method()
786 def remove_field_from_repo(request, apiuser, repoid, key):
786 def remove_field_from_repo(request, apiuser, repoid, key):
787 """
787 """
788 Removes an extra field from a repository.
788 Removes an extra field from a repository.
789
789
790 This command can only be run using an |authtoken| with at least
790 This command can only be run using an |authtoken| with at least
791 write permissions to the |repo|.
791 write permissions to the |repo|.
792
792
793 :param apiuser: This is filled automatically from the |authtoken|.
793 :param apiuser: This is filled automatically from the |authtoken|.
794 :type apiuser: AuthUser
794 :type apiuser: AuthUser
795 :param repoid: Set the repository name or repository ID.
795 :param repoid: Set the repository name or repository ID.
796 :type repoid: str or int
796 :type repoid: str or int
797 :param key: Set the unique field key for this repository.
797 :param key: Set the unique field key for this repository.
798 :type key: str
798 :type key: str
799 """
799 """
800
800
801 repo = get_repo_or_error(repoid)
801 repo = get_repo_or_error(repoid)
802 if not has_superadmin_permission(apiuser):
802 if not has_superadmin_permission(apiuser):
803 _perms = ('repository.admin',)
803 _perms = ('repository.admin',)
804 validate_repo_permissions(apiuser, repoid, repo, _perms)
804 validate_repo_permissions(apiuser, repoid, repo, _perms)
805
805
806 field = RepositoryField.get_by_key_name(key, repo)
806 field = RepositoryField.get_by_key_name(key, repo)
807 if not field:
807 if not field:
808 raise JSONRPCError('Field with key `%s` does not '
808 raise JSONRPCError('Field with key `%s` does not '
809 'exists for repo `%s`' % (key, repoid))
809 'exists for repo `%s`' % (key, repoid))
810
810
811 try:
811 try:
812 RepoModel().delete_repo_field(repo, field_key=key)
812 RepoModel().delete_repo_field(repo, field_key=key)
813 Session().commit()
813 Session().commit()
814 return {
814 return {
815 'msg': "Deleted repository field `%s`" % (key,),
815 'msg': "Deleted repository field `%s`" % (key,),
816 'success': True,
816 'success': True,
817 }
817 }
818 except Exception:
818 except Exception:
819 log.exception(
819 log.exception(
820 "Exception occurred while trying to delete field from repo")
820 "Exception occurred while trying to delete field from repo")
821 raise JSONRPCError(
821 raise JSONRPCError(
822 'failed to delete field for repository `%s`' % (repoid,))
822 'failed to delete field for repository `%s`' % (repoid,))
823
823
824
824
825 @jsonrpc_method()
825 @jsonrpc_method()
826 def update_repo(
826 def update_repo(
827 request, apiuser, repoid, repo_name=Optional(None),
827 request, apiuser, repoid, repo_name=Optional(None),
828 owner=Optional(OAttr('apiuser')), description=Optional(''),
828 owner=Optional(OAttr('apiuser')), description=Optional(''),
829 private=Optional(False), clone_uri=Optional(None),
829 private=Optional(False), clone_uri=Optional(None),
830 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
830 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
831 enable_statistics=Optional(False),
831 enable_statistics=Optional(False),
832 enable_locking=Optional(False),
832 enable_locking=Optional(False),
833 enable_downloads=Optional(False), fields=Optional('')):
833 enable_downloads=Optional(False), fields=Optional('')):
834 """
834 """
835 Updates a repository with the given information.
835 Updates a repository with the given information.
836
836
837 This command can only be run using an |authtoken| with at least
837 This command can only be run using an |authtoken| with at least
838 admin permissions to the |repo|.
838 admin permissions to the |repo|.
839
839
840 * If the repository name contains "/", repository will be updated
840 * If the repository name contains "/", repository will be updated
841 accordingly with a repository group or nested repository groups
841 accordingly with a repository group or nested repository groups
842
842
843 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
843 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
844 called "repo-test" and place it inside group "foo/bar".
844 called "repo-test" and place it inside group "foo/bar".
845 You have to have permissions to access and write to the last repository
845 You have to have permissions to access and write to the last repository
846 group ("bar" in this example)
846 group ("bar" in this example)
847
847
848 :param apiuser: This is filled automatically from the |authtoken|.
848 :param apiuser: This is filled automatically from the |authtoken|.
849 :type apiuser: AuthUser
849 :type apiuser: AuthUser
850 :param repoid: repository name or repository ID.
850 :param repoid: repository name or repository ID.
851 :type repoid: str or int
851 :type repoid: str or int
852 :param repo_name: Update the |repo| name, including the
852 :param repo_name: Update the |repo| name, including the
853 repository group it's in.
853 repository group it's in.
854 :type repo_name: str
854 :type repo_name: str
855 :param owner: Set the |repo| owner.
855 :param owner: Set the |repo| owner.
856 :type owner: str
856 :type owner: str
857 :param fork_of: Set the |repo| as fork of another |repo|.
857 :param fork_of: Set the |repo| as fork of another |repo|.
858 :type fork_of: str
858 :type fork_of: str
859 :param description: Update the |repo| description.
859 :param description: Update the |repo| description.
860 :type description: str
860 :type description: str
861 :param private: Set the |repo| as private. (True | False)
861 :param private: Set the |repo| as private. (True | False)
862 :type private: bool
862 :type private: bool
863 :param clone_uri: Update the |repo| clone URI.
863 :param clone_uri: Update the |repo| clone URI.
864 :type clone_uri: str
864 :type clone_uri: str
865 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
865 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
866 :type landing_rev: str
866 :type landing_rev: str
867 :param enable_statistics: Enable statistics on the |repo|, (True | False).
867 :param enable_statistics: Enable statistics on the |repo|, (True | False).
868 :type enable_statistics: bool
868 :type enable_statistics: bool
869 :param enable_locking: Enable |repo| locking.
869 :param enable_locking: Enable |repo| locking.
870 :type enable_locking: bool
870 :type enable_locking: bool
871 :param enable_downloads: Enable downloads from the |repo|, (True | False).
871 :param enable_downloads: Enable downloads from the |repo|, (True | False).
872 :type enable_downloads: bool
872 :type enable_downloads: bool
873 :param fields: Add extra fields to the |repo|. Use the following
873 :param fields: Add extra fields to the |repo|. Use the following
874 example format: ``field_key=field_val,field_key2=fieldval2``.
874 example format: ``field_key=field_val,field_key2=fieldval2``.
875 Escape ', ' with \,
875 Escape ', ' with \,
876 :type fields: str
876 :type fields: str
877 """
877 """
878
878
879 repo = get_repo_or_error(repoid)
879 repo = get_repo_or_error(repoid)
880
880
881 include_secrets = False
881 include_secrets = False
882 if not has_superadmin_permission(apiuser):
882 if not has_superadmin_permission(apiuser):
883 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
883 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
884 else:
884 else:
885 include_secrets = True
885 include_secrets = True
886
886
887 updates = dict(
887 updates = dict(
888 repo_name=repo_name
888 repo_name=repo_name
889 if not isinstance(repo_name, Optional) else repo.repo_name,
889 if not isinstance(repo_name, Optional) else repo.repo_name,
890
890
891 fork_id=fork_of
891 fork_id=fork_of
892 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
892 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
893
893
894 user=owner
894 user=owner
895 if not isinstance(owner, Optional) else repo.user.username,
895 if not isinstance(owner, Optional) else repo.user.username,
896
896
897 repo_description=description
897 repo_description=description
898 if not isinstance(description, Optional) else repo.description,
898 if not isinstance(description, Optional) else repo.description,
899
899
900 repo_private=private
900 repo_private=private
901 if not isinstance(private, Optional) else repo.private,
901 if not isinstance(private, Optional) else repo.private,
902
902
903 clone_uri=clone_uri
903 clone_uri=clone_uri
904 if not isinstance(clone_uri, Optional) else repo.clone_uri,
904 if not isinstance(clone_uri, Optional) else repo.clone_uri,
905
905
906 repo_landing_rev=landing_rev
906 repo_landing_rev=landing_rev
907 if not isinstance(landing_rev, Optional) else repo._landing_revision,
907 if not isinstance(landing_rev, Optional) else repo._landing_revision,
908
908
909 repo_enable_statistics=enable_statistics
909 repo_enable_statistics=enable_statistics
910 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
910 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
911
911
912 repo_enable_locking=enable_locking
912 repo_enable_locking=enable_locking
913 if not isinstance(enable_locking, Optional) else repo.enable_locking,
913 if not isinstance(enable_locking, Optional) else repo.enable_locking,
914
914
915 repo_enable_downloads=enable_downloads
915 repo_enable_downloads=enable_downloads
916 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
916 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
917
917
918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
919
919
920 schema = repo_schema.RepoSchema().bind(
920 schema = repo_schema.RepoSchema().bind(
921 repo_type_options=rhodecode.BACKENDS.keys(),
921 repo_type_options=rhodecode.BACKENDS.keys(),
922 repo_ref_options=ref_choices,
922 repo_ref_options=ref_choices,
923 # user caller
923 # user caller
924 user=apiuser,
924 user=apiuser,
925 old_values=repo.get_api_data())
925 old_values=repo.get_api_data())
926 try:
926 try:
927 schema_data = schema.deserialize(dict(
927 schema_data = schema.deserialize(dict(
928 # we save old value, users cannot change type
928 # we save old value, users cannot change type
929 repo_type=repo.repo_type,
929 repo_type=repo.repo_type,
930
930
931 repo_name=updates['repo_name'],
931 repo_name=updates['repo_name'],
932 repo_owner=updates['user'],
932 repo_owner=updates['user'],
933 repo_description=updates['repo_description'],
933 repo_description=updates['repo_description'],
934 repo_clone_uri=updates['clone_uri'],
934 repo_clone_uri=updates['clone_uri'],
935 repo_fork_of=updates['fork_id'],
935 repo_fork_of=updates['fork_id'],
936 repo_private=updates['repo_private'],
936 repo_private=updates['repo_private'],
937 repo_landing_commit_ref=updates['repo_landing_rev'],
937 repo_landing_commit_ref=updates['repo_landing_rev'],
938 repo_enable_statistics=updates['repo_enable_statistics'],
938 repo_enable_statistics=updates['repo_enable_statistics'],
939 repo_enable_downloads=updates['repo_enable_downloads'],
939 repo_enable_downloads=updates['repo_enable_downloads'],
940 repo_enable_locking=updates['repo_enable_locking']))
940 repo_enable_locking=updates['repo_enable_locking']))
941 except validation_schema.Invalid as err:
941 except validation_schema.Invalid as err:
942 raise JSONRPCValidationError(colander_exc=err)
942 raise JSONRPCValidationError(colander_exc=err)
943
943
944 # save validated data back into the updates dict
944 # save validated data back into the updates dict
945 validated_updates = dict(
945 validated_updates = dict(
946 repo_name=schema_data['repo_group']['repo_name_without_group'],
946 repo_name=schema_data['repo_group']['repo_name_without_group'],
947 repo_group=schema_data['repo_group']['repo_group_id'],
947 repo_group=schema_data['repo_group']['repo_group_id'],
948
948
949 user=schema_data['repo_owner'],
949 user=schema_data['repo_owner'],
950 repo_description=schema_data['repo_description'],
950 repo_description=schema_data['repo_description'],
951 repo_private=schema_data['repo_private'],
951 repo_private=schema_data['repo_private'],
952 clone_uri=schema_data['repo_clone_uri'],
952 clone_uri=schema_data['repo_clone_uri'],
953 repo_landing_rev=schema_data['repo_landing_commit_ref'],
953 repo_landing_rev=schema_data['repo_landing_commit_ref'],
954 repo_enable_statistics=schema_data['repo_enable_statistics'],
954 repo_enable_statistics=schema_data['repo_enable_statistics'],
955 repo_enable_locking=schema_data['repo_enable_locking'],
955 repo_enable_locking=schema_data['repo_enable_locking'],
956 repo_enable_downloads=schema_data['repo_enable_downloads'],
956 repo_enable_downloads=schema_data['repo_enable_downloads'],
957 )
957 )
958
958
959 if schema_data['repo_fork_of']:
959 if schema_data['repo_fork_of']:
960 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
960 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
961 validated_updates['fork_id'] = fork_repo.repo_id
961 validated_updates['fork_id'] = fork_repo.repo_id
962
962
963 # extra fields
963 # extra fields
964 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
964 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
965 if fields:
965 if fields:
966 validated_updates.update(fields)
966 validated_updates.update(fields)
967
967
968 try:
968 try:
969 RepoModel().update(repo, **validated_updates)
969 RepoModel().update(repo, **validated_updates)
970 Session().commit()
970 Session().commit()
971 return {
971 return {
972 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
972 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
973 'repository': repo.get_api_data(include_secrets=include_secrets)
973 'repository': repo.get_api_data(include_secrets=include_secrets)
974 }
974 }
975 except Exception:
975 except Exception:
976 log.exception(
976 log.exception(
977 u"Exception while trying to update the repository %s",
977 u"Exception while trying to update the repository %s",
978 repoid)
978 repoid)
979 raise JSONRPCError('failed to update repo `%s`' % repoid)
979 raise JSONRPCError('failed to update repo `%s`' % repoid)
980
980
981
981
982 @jsonrpc_method()
982 @jsonrpc_method()
983 def fork_repo(request, apiuser, repoid, fork_name,
983 def fork_repo(request, apiuser, repoid, fork_name,
984 owner=Optional(OAttr('apiuser')),
984 owner=Optional(OAttr('apiuser')),
985 description=Optional(''),
985 description=Optional(''),
986 private=Optional(False),
986 private=Optional(False),
987 clone_uri=Optional(None),
987 clone_uri=Optional(None),
988 landing_rev=Optional('rev:tip'),
988 landing_rev=Optional('rev:tip'),
989 copy_permissions=Optional(False)):
989 copy_permissions=Optional(False)):
990 """
990 """
991 Creates a fork of the specified |repo|.
991 Creates a fork of the specified |repo|.
992
992
993 * If the fork_name contains "/", fork will be created inside
993 * If the fork_name contains "/", fork will be created inside
994 a repository group or nested repository groups
994 a repository group or nested repository groups
995
995
996 For example "foo/bar/fork-repo" will create fork called "fork-repo"
996 For example "foo/bar/fork-repo" will create fork called "fork-repo"
997 inside group "foo/bar". You have to have permissions to access and
997 inside group "foo/bar". You have to have permissions to access and
998 write to the last repository group ("bar" in this example)
998 write to the last repository group ("bar" in this example)
999
999
1000 This command can only be run using an |authtoken| with minimum
1000 This command can only be run using an |authtoken| with minimum
1001 read permissions of the forked repo, create fork permissions for an user.
1001 read permissions of the forked repo, create fork permissions for an user.
1002
1002
1003 :param apiuser: This is filled automatically from the |authtoken|.
1003 :param apiuser: This is filled automatically from the |authtoken|.
1004 :type apiuser: AuthUser
1004 :type apiuser: AuthUser
1005 :param repoid: Set repository name or repository ID.
1005 :param repoid: Set repository name or repository ID.
1006 :type repoid: str or int
1006 :type repoid: str or int
1007 :param fork_name: Set the fork name, including it's repository group membership.
1007 :param fork_name: Set the fork name, including it's repository group membership.
1008 :type fork_name: str
1008 :type fork_name: str
1009 :param owner: Set the fork owner.
1009 :param owner: Set the fork owner.
1010 :type owner: str
1010 :type owner: str
1011 :param description: Set the fork description.
1011 :param description: Set the fork description.
1012 :type description: str
1012 :type description: str
1013 :param copy_permissions: Copy permissions from parent |repo|. The
1013 :param copy_permissions: Copy permissions from parent |repo|. The
1014 default is False.
1014 default is False.
1015 :type copy_permissions: bool
1015 :type copy_permissions: bool
1016 :param private: Make the fork private. The default is False.
1016 :param private: Make the fork private. The default is False.
1017 :type private: bool
1017 :type private: bool
1018 :param landing_rev: Set the landing revision. The default is tip.
1018 :param landing_rev: Set the landing revision. The default is tip.
1019
1019
1020 Example output:
1020 Example output:
1021
1021
1022 .. code-block:: bash
1022 .. code-block:: bash
1023
1023
1024 id : <id_for_response>
1024 id : <id_for_response>
1025 api_key : "<api_key>"
1025 api_key : "<api_key>"
1026 args: {
1026 args: {
1027 "repoid" : "<reponame or repo_id>",
1027 "repoid" : "<reponame or repo_id>",
1028 "fork_name": "<forkname>",
1028 "fork_name": "<forkname>",
1029 "owner": "<username or user_id = Optional(=apiuser)>",
1029 "owner": "<username or user_id = Optional(=apiuser)>",
1030 "description": "<description>",
1030 "description": "<description>",
1031 "copy_permissions": "<bool>",
1031 "copy_permissions": "<bool>",
1032 "private": "<bool>",
1032 "private": "<bool>",
1033 "landing_rev": "<landing_rev>"
1033 "landing_rev": "<landing_rev>"
1034 }
1034 }
1035
1035
1036 Example error output:
1036 Example error output:
1037
1037
1038 .. code-block:: bash
1038 .. code-block:: bash
1039
1039
1040 id : <id_given_in_input>
1040 id : <id_given_in_input>
1041 result: {
1041 result: {
1042 "msg": "Created fork of `<reponame>` as `<forkname>`",
1042 "msg": "Created fork of `<reponame>` as `<forkname>`",
1043 "success": true,
1043 "success": true,
1044 "task": "<celery task id or None if done sync>"
1044 "task": "<celery task id or None if done sync>"
1045 }
1045 }
1046 error: null
1046 error: null
1047
1047
1048 """
1048 """
1049
1049
1050 repo = get_repo_or_error(repoid)
1050 repo = get_repo_or_error(repoid)
1051 repo_name = repo.repo_name
1051 repo_name = repo.repo_name
1052
1052
1053 if not has_superadmin_permission(apiuser):
1053 if not has_superadmin_permission(apiuser):
1054 # check if we have at least read permission for
1054 # check if we have at least read permission for
1055 # this repo that we fork !
1055 # this repo that we fork !
1056 _perms = (
1056 _perms = (
1057 'repository.admin', 'repository.write', 'repository.read')
1057 'repository.admin', 'repository.write', 'repository.read')
1058 validate_repo_permissions(apiuser, repoid, repo, _perms)
1058 validate_repo_permissions(apiuser, repoid, repo, _perms)
1059
1059
1060 # check if the regular user has at least fork permissions as well
1060 # check if the regular user has at least fork permissions as well
1061 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1061 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1062 raise JSONRPCForbidden()
1062 raise JSONRPCForbidden()
1063
1063
1064 # check if user can set owner parameter
1064 # check if user can set owner parameter
1065 owner = validate_set_owner_permissions(apiuser, owner)
1065 owner = validate_set_owner_permissions(apiuser, owner)
1066
1066
1067 description = Optional.extract(description)
1067 description = Optional.extract(description)
1068 copy_permissions = Optional.extract(copy_permissions)
1068 copy_permissions = Optional.extract(copy_permissions)
1069 clone_uri = Optional.extract(clone_uri)
1069 clone_uri = Optional.extract(clone_uri)
1070 landing_commit_ref = Optional.extract(landing_rev)
1070 landing_commit_ref = Optional.extract(landing_rev)
1071 private = Optional.extract(private)
1071 private = Optional.extract(private)
1072
1072
1073 schema = repo_schema.RepoSchema().bind(
1073 schema = repo_schema.RepoSchema().bind(
1074 repo_type_options=rhodecode.BACKENDS.keys(),
1074 repo_type_options=rhodecode.BACKENDS.keys(),
1075 # user caller
1075 # user caller
1076 user=apiuser)
1076 user=apiuser)
1077
1077
1078 try:
1078 try:
1079 schema_data = schema.deserialize(dict(
1079 schema_data = schema.deserialize(dict(
1080 repo_name=fork_name,
1080 repo_name=fork_name,
1081 repo_type=repo.repo_type,
1081 repo_type=repo.repo_type,
1082 repo_owner=owner.username,
1082 repo_owner=owner.username,
1083 repo_description=description,
1083 repo_description=description,
1084 repo_landing_commit_ref=landing_commit_ref,
1084 repo_landing_commit_ref=landing_commit_ref,
1085 repo_clone_uri=clone_uri,
1085 repo_clone_uri=clone_uri,
1086 repo_private=private,
1086 repo_private=private,
1087 repo_copy_permissions=copy_permissions))
1087 repo_copy_permissions=copy_permissions))
1088 except validation_schema.Invalid as err:
1088 except validation_schema.Invalid as err:
1089 raise JSONRPCValidationError(colander_exc=err)
1089 raise JSONRPCValidationError(colander_exc=err)
1090
1090
1091 try:
1091 try:
1092 data = {
1092 data = {
1093 'fork_parent_id': repo.repo_id,
1093 'fork_parent_id': repo.repo_id,
1094
1094
1095 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1095 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1096 'repo_name_full': schema_data['repo_name'],
1096 'repo_name_full': schema_data['repo_name'],
1097 'repo_group': schema_data['repo_group']['repo_group_id'],
1097 'repo_group': schema_data['repo_group']['repo_group_id'],
1098 'repo_type': schema_data['repo_type'],
1098 'repo_type': schema_data['repo_type'],
1099 'description': schema_data['repo_description'],
1099 'description': schema_data['repo_description'],
1100 'private': schema_data['repo_private'],
1100 'private': schema_data['repo_private'],
1101 'copy_permissions': schema_data['repo_copy_permissions'],
1101 'copy_permissions': schema_data['repo_copy_permissions'],
1102 'landing_rev': schema_data['repo_landing_commit_ref'],
1102 'landing_rev': schema_data['repo_landing_commit_ref'],
1103 }
1103 }
1104
1104
1105 task = RepoModel().create_fork(data, cur_user=owner)
1105 task = RepoModel().create_fork(data, cur_user=owner)
1106 # no commit, it's done in RepoModel, or async via celery
1106 # no commit, it's done in RepoModel, or async via celery
1107 from celery.result import BaseAsyncResult
1107 from celery.result import BaseAsyncResult
1108 task_id = None
1108 task_id = None
1109 if isinstance(task, BaseAsyncResult):
1109 if isinstance(task, BaseAsyncResult):
1110 task_id = task.task_id
1110 task_id = task.task_id
1111 return {
1111 return {
1112 'msg': 'Created fork of `%s` as `%s`' % (
1112 'msg': 'Created fork of `%s` as `%s`' % (
1113 repo.repo_name, schema_data['repo_name']),
1113 repo.repo_name, schema_data['repo_name']),
1114 'success': True, # cannot return the repo data here since fork
1114 'success': True, # cannot return the repo data here since fork
1115 # can be done async
1115 # can be done async
1116 'task': task_id
1116 'task': task_id
1117 }
1117 }
1118 except Exception:
1118 except Exception:
1119 log.exception(
1119 log.exception(
1120 u"Exception while trying to create fork %s",
1120 u"Exception while trying to create fork %s",
1121 schema_data['repo_name'])
1121 schema_data['repo_name'])
1122 raise JSONRPCError(
1122 raise JSONRPCError(
1123 'failed to fork repository `%s` as `%s`' % (
1123 'failed to fork repository `%s` as `%s`' % (
1124 repo_name, schema_data['repo_name']))
1124 repo_name, schema_data['repo_name']))
1125
1125
1126
1126
1127 @jsonrpc_method()
1127 @jsonrpc_method()
1128 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1128 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1129 """
1129 """
1130 Deletes a repository.
1130 Deletes a repository.
1131
1131
1132 * When the `forks` parameter is set it's possible to detach or delete
1132 * When the `forks` parameter is set it's possible to detach or delete
1133 forks of deleted repository.
1133 forks of deleted repository.
1134
1134
1135 This command can only be run using an |authtoken| with admin
1135 This command can only be run using an |authtoken| with admin
1136 permissions on the |repo|.
1136 permissions on the |repo|.
1137
1137
1138 :param apiuser: This is filled automatically from the |authtoken|.
1138 :param apiuser: This is filled automatically from the |authtoken|.
1139 :type apiuser: AuthUser
1139 :type apiuser: AuthUser
1140 :param repoid: Set the repository name or repository ID.
1140 :param repoid: Set the repository name or repository ID.
1141 :type repoid: str or int
1141 :type repoid: str or int
1142 :param forks: Set to `detach` or `delete` forks from the |repo|.
1142 :param forks: Set to `detach` or `delete` forks from the |repo|.
1143 :type forks: Optional(str)
1143 :type forks: Optional(str)
1144
1144
1145 Example error output:
1145 Example error output:
1146
1146
1147 .. code-block:: bash
1147 .. code-block:: bash
1148
1148
1149 id : <id_given_in_input>
1149 id : <id_given_in_input>
1150 result: {
1150 result: {
1151 "msg": "Deleted repository `<reponame>`",
1151 "msg": "Deleted repository `<reponame>`",
1152 "success": true
1152 "success": true
1153 }
1153 }
1154 error: null
1154 error: null
1155 """
1155 """
1156
1156
1157 repo = get_repo_or_error(repoid)
1157 repo = get_repo_or_error(repoid)
1158 repo_name = repo.repo_name
1158 repo_name = repo.repo_name
1159 if not has_superadmin_permission(apiuser):
1159 if not has_superadmin_permission(apiuser):
1160 _perms = ('repository.admin',)
1160 _perms = ('repository.admin',)
1161 validate_repo_permissions(apiuser, repoid, repo, _perms)
1161 validate_repo_permissions(apiuser, repoid, repo, _perms)
1162
1162
1163 try:
1163 try:
1164 handle_forks = Optional.extract(forks)
1164 handle_forks = Optional.extract(forks)
1165 _forks_msg = ''
1165 _forks_msg = ''
1166 _forks = [f for f in repo.forks]
1166 _forks = [f for f in repo.forks]
1167 if handle_forks == 'detach':
1167 if handle_forks == 'detach':
1168 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1168 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1169 elif handle_forks == 'delete':
1169 elif handle_forks == 'delete':
1170 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1170 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1171 elif _forks:
1171 elif _forks:
1172 raise JSONRPCError(
1172 raise JSONRPCError(
1173 'Cannot delete `%s` it still contains attached forks' %
1173 'Cannot delete `%s` it still contains attached forks' %
1174 (repo.repo_name,)
1174 (repo.repo_name,)
1175 )
1175 )
1176 repo_data = repo.get_api_data()
1176 repo_data = repo.get_api_data()
1177 RepoModel().delete(repo, forks=forks)
1177 RepoModel().delete(repo, forks=forks)
1178
1178
1179 repo = audit_logger.RepoWrap(repo_id=None,
1179 repo = audit_logger.RepoWrap(repo_id=None,
1180 repo_name=repo.repo_name)
1180 repo_name=repo.repo_name)
1181
1181
1182 audit_logger.store(
1182 audit_logger.store_api(
1183 action='repo.delete',
1183 action='repo.delete',
1184 action_data={'repo_data': repo_data,
1184 action_data={'data': repo_data},
1185 'source': audit_logger.SOURCE_API},
1185 user=apiuser, repo=repo)
1186 user=apiuser, repo=repo, commit=False)
1187
1186
1188 ScmModel().mark_for_invalidation(repo_name, delete=True)
1187 ScmModel().mark_for_invalidation(repo_name, delete=True)
1189 Session().commit()
1188 Session().commit()
1190 return {
1189 return {
1191 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1190 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1192 'success': True
1191 'success': True
1193 }
1192 }
1194 except Exception:
1193 except Exception:
1195 log.exception("Exception occurred while trying to delete repo")
1194 log.exception("Exception occurred while trying to delete repo")
1196 raise JSONRPCError(
1195 raise JSONRPCError(
1197 'failed to delete repository `%s`' % (repo_name,)
1196 'failed to delete repository `%s`' % (repo_name,)
1198 )
1197 )
1199
1198
1200
1199
1201 #TODO: marcink, change name ?
1200 #TODO: marcink, change name ?
1202 @jsonrpc_method()
1201 @jsonrpc_method()
1203 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1202 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1204 """
1203 """
1205 Invalidates the cache for the specified repository.
1204 Invalidates the cache for the specified repository.
1206
1205
1207 This command can only be run using an |authtoken| with admin rights to
1206 This command can only be run using an |authtoken| with admin rights to
1208 the specified repository.
1207 the specified repository.
1209
1208
1210 This command takes the following options:
1209 This command takes the following options:
1211
1210
1212 :param apiuser: This is filled automatically from |authtoken|.
1211 :param apiuser: This is filled automatically from |authtoken|.
1213 :type apiuser: AuthUser
1212 :type apiuser: AuthUser
1214 :param repoid: Sets the repository name or repository ID.
1213 :param repoid: Sets the repository name or repository ID.
1215 :type repoid: str or int
1214 :type repoid: str or int
1216 :param delete_keys: This deletes the invalidated keys instead of
1215 :param delete_keys: This deletes the invalidated keys instead of
1217 just flagging them.
1216 just flagging them.
1218 :type delete_keys: Optional(``True`` | ``False``)
1217 :type delete_keys: Optional(``True`` | ``False``)
1219
1218
1220 Example output:
1219 Example output:
1221
1220
1222 .. code-block:: bash
1221 .. code-block:: bash
1223
1222
1224 id : <id_given_in_input>
1223 id : <id_given_in_input>
1225 result : {
1224 result : {
1226 'msg': Cache for repository `<repository name>` was invalidated,
1225 'msg': Cache for repository `<repository name>` was invalidated,
1227 'repository': <repository name>
1226 'repository': <repository name>
1228 }
1227 }
1229 error : null
1228 error : null
1230
1229
1231 Example error output:
1230 Example error output:
1232
1231
1233 .. code-block:: bash
1232 .. code-block:: bash
1234
1233
1235 id : <id_given_in_input>
1234 id : <id_given_in_input>
1236 result : null
1235 result : null
1237 error : {
1236 error : {
1238 'Error occurred during cache invalidation action'
1237 'Error occurred during cache invalidation action'
1239 }
1238 }
1240
1239
1241 """
1240 """
1242
1241
1243 repo = get_repo_or_error(repoid)
1242 repo = get_repo_or_error(repoid)
1244 if not has_superadmin_permission(apiuser):
1243 if not has_superadmin_permission(apiuser):
1245 _perms = ('repository.admin', 'repository.write',)
1244 _perms = ('repository.admin', 'repository.write',)
1246 validate_repo_permissions(apiuser, repoid, repo, _perms)
1245 validate_repo_permissions(apiuser, repoid, repo, _perms)
1247
1246
1248 delete = Optional.extract(delete_keys)
1247 delete = Optional.extract(delete_keys)
1249 try:
1248 try:
1250 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1249 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1251 return {
1250 return {
1252 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1251 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1253 'repository': repo.repo_name
1252 'repository': repo.repo_name
1254 }
1253 }
1255 except Exception:
1254 except Exception:
1256 log.exception(
1255 log.exception(
1257 "Exception occurred while trying to invalidate repo cache")
1256 "Exception occurred while trying to invalidate repo cache")
1258 raise JSONRPCError(
1257 raise JSONRPCError(
1259 'Error occurred during cache invalidation action'
1258 'Error occurred during cache invalidation action'
1260 )
1259 )
1261
1260
1262
1261
1263 #TODO: marcink, change name ?
1262 #TODO: marcink, change name ?
1264 @jsonrpc_method()
1263 @jsonrpc_method()
1265 def lock(request, apiuser, repoid, locked=Optional(None),
1264 def lock(request, apiuser, repoid, locked=Optional(None),
1266 userid=Optional(OAttr('apiuser'))):
1265 userid=Optional(OAttr('apiuser'))):
1267 """
1266 """
1268 Sets the lock state of the specified |repo| by the given user.
1267 Sets the lock state of the specified |repo| by the given user.
1269 From more information, see :ref:`repo-locking`.
1268 From more information, see :ref:`repo-locking`.
1270
1269
1271 * If the ``userid`` option is not set, the repository is locked to the
1270 * If the ``userid`` option is not set, the repository is locked to the
1272 user who called the method.
1271 user who called the method.
1273 * If the ``locked`` parameter is not set, the current lock state of the
1272 * If the ``locked`` parameter is not set, the current lock state of the
1274 repository is displayed.
1273 repository is displayed.
1275
1274
1276 This command can only be run using an |authtoken| with admin rights to
1275 This command can only be run using an |authtoken| with admin rights to
1277 the specified repository.
1276 the specified repository.
1278
1277
1279 This command takes the following options:
1278 This command takes the following options:
1280
1279
1281 :param apiuser: This is filled automatically from the |authtoken|.
1280 :param apiuser: This is filled automatically from the |authtoken|.
1282 :type apiuser: AuthUser
1281 :type apiuser: AuthUser
1283 :param repoid: Sets the repository name or repository ID.
1282 :param repoid: Sets the repository name or repository ID.
1284 :type repoid: str or int
1283 :type repoid: str or int
1285 :param locked: Sets the lock state.
1284 :param locked: Sets the lock state.
1286 :type locked: Optional(``True`` | ``False``)
1285 :type locked: Optional(``True`` | ``False``)
1287 :param userid: Set the repository lock to this user.
1286 :param userid: Set the repository lock to this user.
1288 :type userid: Optional(str or int)
1287 :type userid: Optional(str or int)
1289
1288
1290 Example error output:
1289 Example error output:
1291
1290
1292 .. code-block:: bash
1291 .. code-block:: bash
1293
1292
1294 id : <id_given_in_input>
1293 id : <id_given_in_input>
1295 result : {
1294 result : {
1296 'repo': '<reponame>',
1295 'repo': '<reponame>',
1297 'locked': <bool: lock state>,
1296 'locked': <bool: lock state>,
1298 'locked_since': <int: lock timestamp>,
1297 'locked_since': <int: lock timestamp>,
1299 'locked_by': <username of person who made the lock>,
1298 'locked_by': <username of person who made the lock>,
1300 'lock_reason': <str: reason for locking>,
1299 'lock_reason': <str: reason for locking>,
1301 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1300 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1302 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1301 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1303 or
1302 or
1304 'msg': 'Repo `<repository name>` not locked.'
1303 'msg': 'Repo `<repository name>` not locked.'
1305 or
1304 or
1306 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1305 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1307 }
1306 }
1308 error : null
1307 error : null
1309
1308
1310 Example error output:
1309 Example error output:
1311
1310
1312 .. code-block:: bash
1311 .. code-block:: bash
1313
1312
1314 id : <id_given_in_input>
1313 id : <id_given_in_input>
1315 result : null
1314 result : null
1316 error : {
1315 error : {
1317 'Error occurred locking repository `<reponame>`'
1316 'Error occurred locking repository `<reponame>`'
1318 }
1317 }
1319 """
1318 """
1320
1319
1321 repo = get_repo_or_error(repoid)
1320 repo = get_repo_or_error(repoid)
1322 if not has_superadmin_permission(apiuser):
1321 if not has_superadmin_permission(apiuser):
1323 # check if we have at least write permission for this repo !
1322 # check if we have at least write permission for this repo !
1324 _perms = ('repository.admin', 'repository.write',)
1323 _perms = ('repository.admin', 'repository.write',)
1325 validate_repo_permissions(apiuser, repoid, repo, _perms)
1324 validate_repo_permissions(apiuser, repoid, repo, _perms)
1326
1325
1327 # make sure normal user does not pass someone else userid,
1326 # make sure normal user does not pass someone else userid,
1328 # he is not allowed to do that
1327 # he is not allowed to do that
1329 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1328 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1330 raise JSONRPCError('userid is not the same as your user')
1329 raise JSONRPCError('userid is not the same as your user')
1331
1330
1332 if isinstance(userid, Optional):
1331 if isinstance(userid, Optional):
1333 userid = apiuser.user_id
1332 userid = apiuser.user_id
1334
1333
1335 user = get_user_or_error(userid)
1334 user = get_user_or_error(userid)
1336
1335
1337 if isinstance(locked, Optional):
1336 if isinstance(locked, Optional):
1338 lockobj = repo.locked
1337 lockobj = repo.locked
1339
1338
1340 if lockobj[0] is None:
1339 if lockobj[0] is None:
1341 _d = {
1340 _d = {
1342 'repo': repo.repo_name,
1341 'repo': repo.repo_name,
1343 'locked': False,
1342 'locked': False,
1344 'locked_since': None,
1343 'locked_since': None,
1345 'locked_by': None,
1344 'locked_by': None,
1346 'lock_reason': None,
1345 'lock_reason': None,
1347 'lock_state_changed': False,
1346 'lock_state_changed': False,
1348 'msg': 'Repo `%s` not locked.' % repo.repo_name
1347 'msg': 'Repo `%s` not locked.' % repo.repo_name
1349 }
1348 }
1350 return _d
1349 return _d
1351 else:
1350 else:
1352 _user_id, _time, _reason = lockobj
1351 _user_id, _time, _reason = lockobj
1353 lock_user = get_user_or_error(userid)
1352 lock_user = get_user_or_error(userid)
1354 _d = {
1353 _d = {
1355 'repo': repo.repo_name,
1354 'repo': repo.repo_name,
1356 'locked': True,
1355 'locked': True,
1357 'locked_since': _time,
1356 'locked_since': _time,
1358 'locked_by': lock_user.username,
1357 'locked_by': lock_user.username,
1359 'lock_reason': _reason,
1358 'lock_reason': _reason,
1360 'lock_state_changed': False,
1359 'lock_state_changed': False,
1361 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1360 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1362 % (repo.repo_name, lock_user.username,
1361 % (repo.repo_name, lock_user.username,
1363 json.dumps(time_to_datetime(_time))))
1362 json.dumps(time_to_datetime(_time))))
1364 }
1363 }
1365 return _d
1364 return _d
1366
1365
1367 # force locked state through a flag
1366 # force locked state through a flag
1368 else:
1367 else:
1369 locked = str2bool(locked)
1368 locked = str2bool(locked)
1370 lock_reason = Repository.LOCK_API
1369 lock_reason = Repository.LOCK_API
1371 try:
1370 try:
1372 if locked:
1371 if locked:
1373 lock_time = time.time()
1372 lock_time = time.time()
1374 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1373 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1375 else:
1374 else:
1376 lock_time = None
1375 lock_time = None
1377 Repository.unlock(repo)
1376 Repository.unlock(repo)
1378 _d = {
1377 _d = {
1379 'repo': repo.repo_name,
1378 'repo': repo.repo_name,
1380 'locked': locked,
1379 'locked': locked,
1381 'locked_since': lock_time,
1380 'locked_since': lock_time,
1382 'locked_by': user.username,
1381 'locked_by': user.username,
1383 'lock_reason': lock_reason,
1382 'lock_reason': lock_reason,
1384 'lock_state_changed': True,
1383 'lock_state_changed': True,
1385 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1384 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1386 % (user.username, repo.repo_name, locked))
1385 % (user.username, repo.repo_name, locked))
1387 }
1386 }
1388 return _d
1387 return _d
1389 except Exception:
1388 except Exception:
1390 log.exception(
1389 log.exception(
1391 "Exception occurred while trying to lock repository")
1390 "Exception occurred while trying to lock repository")
1392 raise JSONRPCError(
1391 raise JSONRPCError(
1393 'Error occurred locking repository `%s`' % repo.repo_name
1392 'Error occurred locking repository `%s`' % repo.repo_name
1394 )
1393 )
1395
1394
1396
1395
1397 @jsonrpc_method()
1396 @jsonrpc_method()
1398 def comment_commit(
1397 def comment_commit(
1399 request, apiuser, repoid, commit_id, message, status=Optional(None),
1398 request, apiuser, repoid, commit_id, message, status=Optional(None),
1400 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1399 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1401 resolves_comment_id=Optional(None),
1400 resolves_comment_id=Optional(None),
1402 userid=Optional(OAttr('apiuser'))):
1401 userid=Optional(OAttr('apiuser'))):
1403 """
1402 """
1404 Set a commit comment, and optionally change the status of the commit.
1403 Set a commit comment, and optionally change the status of the commit.
1405
1404
1406 :param apiuser: This is filled automatically from the |authtoken|.
1405 :param apiuser: This is filled automatically from the |authtoken|.
1407 :type apiuser: AuthUser
1406 :type apiuser: AuthUser
1408 :param repoid: Set the repository name or repository ID.
1407 :param repoid: Set the repository name or repository ID.
1409 :type repoid: str or int
1408 :type repoid: str or int
1410 :param commit_id: Specify the commit_id for which to set a comment.
1409 :param commit_id: Specify the commit_id for which to set a comment.
1411 :type commit_id: str
1410 :type commit_id: str
1412 :param message: The comment text.
1411 :param message: The comment text.
1413 :type message: str
1412 :type message: str
1414 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1413 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1415 'approved', 'rejected', 'under_review'
1414 'approved', 'rejected', 'under_review'
1416 :type status: str
1415 :type status: str
1417 :param comment_type: Comment type, one of: 'note', 'todo'
1416 :param comment_type: Comment type, one of: 'note', 'todo'
1418 :type comment_type: Optional(str), default: 'note'
1417 :type comment_type: Optional(str), default: 'note'
1419 :param userid: Set the user name of the comment creator.
1418 :param userid: Set the user name of the comment creator.
1420 :type userid: Optional(str or int)
1419 :type userid: Optional(str or int)
1421
1420
1422 Example error output:
1421 Example error output:
1423
1422
1424 .. code-block:: bash
1423 .. code-block:: bash
1425
1424
1426 {
1425 {
1427 "id" : <id_given_in_input>,
1426 "id" : <id_given_in_input>,
1428 "result" : {
1427 "result" : {
1429 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1428 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1430 "status_change": null or <status>,
1429 "status_change": null or <status>,
1431 "success": true
1430 "success": true
1432 },
1431 },
1433 "error" : null
1432 "error" : null
1434 }
1433 }
1435
1434
1436 """
1435 """
1437 repo = get_repo_or_error(repoid)
1436 repo = get_repo_or_error(repoid)
1438 if not has_superadmin_permission(apiuser):
1437 if not has_superadmin_permission(apiuser):
1439 _perms = ('repository.read', 'repository.write', 'repository.admin')
1438 _perms = ('repository.read', 'repository.write', 'repository.admin')
1440 validate_repo_permissions(apiuser, repoid, repo, _perms)
1439 validate_repo_permissions(apiuser, repoid, repo, _perms)
1441
1440
1442 try:
1441 try:
1443 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1442 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1444 except Exception as e:
1443 except Exception as e:
1445 log.exception('Failed to fetch commit')
1444 log.exception('Failed to fetch commit')
1446 raise JSONRPCError(e.message)
1445 raise JSONRPCError(e.message)
1447
1446
1448 if isinstance(userid, Optional):
1447 if isinstance(userid, Optional):
1449 userid = apiuser.user_id
1448 userid = apiuser.user_id
1450
1449
1451 user = get_user_or_error(userid)
1450 user = get_user_or_error(userid)
1452 status = Optional.extract(status)
1451 status = Optional.extract(status)
1453 comment_type = Optional.extract(comment_type)
1452 comment_type = Optional.extract(comment_type)
1454 resolves_comment_id = Optional.extract(resolves_comment_id)
1453 resolves_comment_id = Optional.extract(resolves_comment_id)
1455
1454
1456 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1455 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1457 if status and status not in allowed_statuses:
1456 if status and status not in allowed_statuses:
1458 raise JSONRPCError('Bad status, must be on '
1457 raise JSONRPCError('Bad status, must be on '
1459 'of %s got %s' % (allowed_statuses, status,))
1458 'of %s got %s' % (allowed_statuses, status,))
1460
1459
1461 if resolves_comment_id:
1460 if resolves_comment_id:
1462 comment = ChangesetComment.get(resolves_comment_id)
1461 comment = ChangesetComment.get(resolves_comment_id)
1463 if not comment:
1462 if not comment:
1464 raise JSONRPCError(
1463 raise JSONRPCError(
1465 'Invalid resolves_comment_id `%s` for this commit.'
1464 'Invalid resolves_comment_id `%s` for this commit.'
1466 % resolves_comment_id)
1465 % resolves_comment_id)
1467 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1466 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1468 raise JSONRPCError(
1467 raise JSONRPCError(
1469 'Comment `%s` is wrong type for setting status to resolved.'
1468 'Comment `%s` is wrong type for setting status to resolved.'
1470 % resolves_comment_id)
1469 % resolves_comment_id)
1471
1470
1472 try:
1471 try:
1473 rc_config = SettingsModel().get_all_settings()
1472 rc_config = SettingsModel().get_all_settings()
1474 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1473 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1475 status_change_label = ChangesetStatus.get_status_lbl(status)
1474 status_change_label = ChangesetStatus.get_status_lbl(status)
1476 comm = CommentsModel().create(
1475 comm = CommentsModel().create(
1477 message, repo, user, commit_id=commit_id,
1476 message, repo, user, commit_id=commit_id,
1478 status_change=status_change_label,
1477 status_change=status_change_label,
1479 status_change_type=status,
1478 status_change_type=status,
1480 renderer=renderer,
1479 renderer=renderer,
1481 comment_type=comment_type,
1480 comment_type=comment_type,
1482 resolves_comment_id=resolves_comment_id
1481 resolves_comment_id=resolves_comment_id
1483 )
1482 )
1484 if status:
1483 if status:
1485 # also do a status change
1484 # also do a status change
1486 try:
1485 try:
1487 ChangesetStatusModel().set_status(
1486 ChangesetStatusModel().set_status(
1488 repo, status, user, comm, revision=commit_id,
1487 repo, status, user, comm, revision=commit_id,
1489 dont_allow_on_closed_pull_request=True
1488 dont_allow_on_closed_pull_request=True
1490 )
1489 )
1491 except StatusChangeOnClosedPullRequestError:
1490 except StatusChangeOnClosedPullRequestError:
1492 log.exception(
1491 log.exception(
1493 "Exception occurred while trying to change repo commit status")
1492 "Exception occurred while trying to change repo commit status")
1494 msg = ('Changing status on a changeset associated with '
1493 msg = ('Changing status on a changeset associated with '
1495 'a closed pull request is not allowed')
1494 'a closed pull request is not allowed')
1496 raise JSONRPCError(msg)
1495 raise JSONRPCError(msg)
1497
1496
1498 Session().commit()
1497 Session().commit()
1499 return {
1498 return {
1500 'msg': (
1499 'msg': (
1501 'Commented on commit `%s` for repository `%s`' % (
1500 'Commented on commit `%s` for repository `%s`' % (
1502 comm.revision, repo.repo_name)),
1501 comm.revision, repo.repo_name)),
1503 'status_change': status,
1502 'status_change': status,
1504 'success': True,
1503 'success': True,
1505 }
1504 }
1506 except JSONRPCError:
1505 except JSONRPCError:
1507 # catch any inside errors, and re-raise them to prevent from
1506 # catch any inside errors, and re-raise them to prevent from
1508 # below global catch to silence them
1507 # below global catch to silence them
1509 raise
1508 raise
1510 except Exception:
1509 except Exception:
1511 log.exception("Exception occurred while trying to comment on commit")
1510 log.exception("Exception occurred while trying to comment on commit")
1512 raise JSONRPCError(
1511 raise JSONRPCError(
1513 'failed to set comment on repository `%s`' % (repo.repo_name,)
1512 'failed to set comment on repository `%s`' % (repo.repo_name,)
1514 )
1513 )
1515
1514
1516
1515
1517 @jsonrpc_method()
1516 @jsonrpc_method()
1518 def grant_user_permission(request, apiuser, repoid, userid, perm):
1517 def grant_user_permission(request, apiuser, repoid, userid, perm):
1519 """
1518 """
1520 Grant permissions for the specified user on the given repository,
1519 Grant permissions for the specified user on the given repository,
1521 or update existing permissions if found.
1520 or update existing permissions if found.
1522
1521
1523 This command can only be run using an |authtoken| with admin
1522 This command can only be run using an |authtoken| with admin
1524 permissions on the |repo|.
1523 permissions on the |repo|.
1525
1524
1526 :param apiuser: This is filled automatically from the |authtoken|.
1525 :param apiuser: This is filled automatically from the |authtoken|.
1527 :type apiuser: AuthUser
1526 :type apiuser: AuthUser
1528 :param repoid: Set the repository name or repository ID.
1527 :param repoid: Set the repository name or repository ID.
1529 :type repoid: str or int
1528 :type repoid: str or int
1530 :param userid: Set the user name.
1529 :param userid: Set the user name.
1531 :type userid: str
1530 :type userid: str
1532 :param perm: Set the user permissions, using the following format
1531 :param perm: Set the user permissions, using the following format
1533 ``(repository.(none|read|write|admin))``
1532 ``(repository.(none|read|write|admin))``
1534 :type perm: str
1533 :type perm: str
1535
1534
1536 Example output:
1535 Example output:
1537
1536
1538 .. code-block:: bash
1537 .. code-block:: bash
1539
1538
1540 id : <id_given_in_input>
1539 id : <id_given_in_input>
1541 result: {
1540 result: {
1542 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1541 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1543 "success": true
1542 "success": true
1544 }
1543 }
1545 error: null
1544 error: null
1546 """
1545 """
1547
1546
1548 repo = get_repo_or_error(repoid)
1547 repo = get_repo_or_error(repoid)
1549 user = get_user_or_error(userid)
1548 user = get_user_or_error(userid)
1550 perm = get_perm_or_error(perm)
1549 perm = get_perm_or_error(perm)
1551 if not has_superadmin_permission(apiuser):
1550 if not has_superadmin_permission(apiuser):
1552 _perms = ('repository.admin',)
1551 _perms = ('repository.admin',)
1553 validate_repo_permissions(apiuser, repoid, repo, _perms)
1552 validate_repo_permissions(apiuser, repoid, repo, _perms)
1554
1553
1555 try:
1554 try:
1556
1555
1557 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1556 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1558
1557
1559 Session().commit()
1558 Session().commit()
1560 return {
1559 return {
1561 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1560 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1562 perm.permission_name, user.username, repo.repo_name
1561 perm.permission_name, user.username, repo.repo_name
1563 ),
1562 ),
1564 'success': True
1563 'success': True
1565 }
1564 }
1566 except Exception:
1565 except Exception:
1567 log.exception(
1566 log.exception(
1568 "Exception occurred while trying edit permissions for repo")
1567 "Exception occurred while trying edit permissions for repo")
1569 raise JSONRPCError(
1568 raise JSONRPCError(
1570 'failed to edit permission for user: `%s` in repo: `%s`' % (
1569 'failed to edit permission for user: `%s` in repo: `%s`' % (
1571 userid, repoid
1570 userid, repoid
1572 )
1571 )
1573 )
1572 )
1574
1573
1575
1574
1576 @jsonrpc_method()
1575 @jsonrpc_method()
1577 def revoke_user_permission(request, apiuser, repoid, userid):
1576 def revoke_user_permission(request, apiuser, repoid, userid):
1578 """
1577 """
1579 Revoke permission for a user on the specified repository.
1578 Revoke permission for a user on the specified repository.
1580
1579
1581 This command can only be run using an |authtoken| with admin
1580 This command can only be run using an |authtoken| with admin
1582 permissions on the |repo|.
1581 permissions on the |repo|.
1583
1582
1584 :param apiuser: This is filled automatically from the |authtoken|.
1583 :param apiuser: This is filled automatically from the |authtoken|.
1585 :type apiuser: AuthUser
1584 :type apiuser: AuthUser
1586 :param repoid: Set the repository name or repository ID.
1585 :param repoid: Set the repository name or repository ID.
1587 :type repoid: str or int
1586 :type repoid: str or int
1588 :param userid: Set the user name of revoked user.
1587 :param userid: Set the user name of revoked user.
1589 :type userid: str or int
1588 :type userid: str or int
1590
1589
1591 Example error output:
1590 Example error output:
1592
1591
1593 .. code-block:: bash
1592 .. code-block:: bash
1594
1593
1595 id : <id_given_in_input>
1594 id : <id_given_in_input>
1596 result: {
1595 result: {
1597 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1596 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1598 "success": true
1597 "success": true
1599 }
1598 }
1600 error: null
1599 error: null
1601 """
1600 """
1602
1601
1603 repo = get_repo_or_error(repoid)
1602 repo = get_repo_or_error(repoid)
1604 user = get_user_or_error(userid)
1603 user = get_user_or_error(userid)
1605 if not has_superadmin_permission(apiuser):
1604 if not has_superadmin_permission(apiuser):
1606 _perms = ('repository.admin',)
1605 _perms = ('repository.admin',)
1607 validate_repo_permissions(apiuser, repoid, repo, _perms)
1606 validate_repo_permissions(apiuser, repoid, repo, _perms)
1608
1607
1609 try:
1608 try:
1610 RepoModel().revoke_user_permission(repo=repo, user=user)
1609 RepoModel().revoke_user_permission(repo=repo, user=user)
1611 Session().commit()
1610 Session().commit()
1612 return {
1611 return {
1613 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1612 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1614 user.username, repo.repo_name
1613 user.username, repo.repo_name
1615 ),
1614 ),
1616 'success': True
1615 'success': True
1617 }
1616 }
1618 except Exception:
1617 except Exception:
1619 log.exception(
1618 log.exception(
1620 "Exception occurred while trying revoke permissions to repo")
1619 "Exception occurred while trying revoke permissions to repo")
1621 raise JSONRPCError(
1620 raise JSONRPCError(
1622 'failed to edit permission for user: `%s` in repo: `%s`' % (
1621 'failed to edit permission for user: `%s` in repo: `%s`' % (
1623 userid, repoid
1622 userid, repoid
1624 )
1623 )
1625 )
1624 )
1626
1625
1627
1626
1628 @jsonrpc_method()
1627 @jsonrpc_method()
1629 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1628 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1630 """
1629 """
1631 Grant permission for a user group on the specified repository,
1630 Grant permission for a user group on the specified repository,
1632 or update existing permissions.
1631 or update existing permissions.
1633
1632
1634 This command can only be run using an |authtoken| with admin
1633 This command can only be run using an |authtoken| with admin
1635 permissions on the |repo|.
1634 permissions on the |repo|.
1636
1635
1637 :param apiuser: This is filled automatically from the |authtoken|.
1636 :param apiuser: This is filled automatically from the |authtoken|.
1638 :type apiuser: AuthUser
1637 :type apiuser: AuthUser
1639 :param repoid: Set the repository name or repository ID.
1638 :param repoid: Set the repository name or repository ID.
1640 :type repoid: str or int
1639 :type repoid: str or int
1641 :param usergroupid: Specify the ID of the user group.
1640 :param usergroupid: Specify the ID of the user group.
1642 :type usergroupid: str or int
1641 :type usergroupid: str or int
1643 :param perm: Set the user group permissions using the following
1642 :param perm: Set the user group permissions using the following
1644 format: (repository.(none|read|write|admin))
1643 format: (repository.(none|read|write|admin))
1645 :type perm: str
1644 :type perm: str
1646
1645
1647 Example output:
1646 Example output:
1648
1647
1649 .. code-block:: bash
1648 .. code-block:: bash
1650
1649
1651 id : <id_given_in_input>
1650 id : <id_given_in_input>
1652 result : {
1651 result : {
1653 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1652 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1654 "success": true
1653 "success": true
1655
1654
1656 }
1655 }
1657 error : null
1656 error : null
1658
1657
1659 Example error output:
1658 Example error output:
1660
1659
1661 .. code-block:: bash
1660 .. code-block:: bash
1662
1661
1663 id : <id_given_in_input>
1662 id : <id_given_in_input>
1664 result : null
1663 result : null
1665 error : {
1664 error : {
1666 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1665 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1667 }
1666 }
1668
1667
1669 """
1668 """
1670
1669
1671 repo = get_repo_or_error(repoid)
1670 repo = get_repo_or_error(repoid)
1672 perm = get_perm_or_error(perm)
1671 perm = get_perm_or_error(perm)
1673 if not has_superadmin_permission(apiuser):
1672 if not has_superadmin_permission(apiuser):
1674 _perms = ('repository.admin',)
1673 _perms = ('repository.admin',)
1675 validate_repo_permissions(apiuser, repoid, repo, _perms)
1674 validate_repo_permissions(apiuser, repoid, repo, _perms)
1676
1675
1677 user_group = get_user_group_or_error(usergroupid)
1676 user_group = get_user_group_or_error(usergroupid)
1678 if not has_superadmin_permission(apiuser):
1677 if not has_superadmin_permission(apiuser):
1679 # check if we have at least read permission for this user group !
1678 # check if we have at least read permission for this user group !
1680 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1679 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1681 if not HasUserGroupPermissionAnyApi(*_perms)(
1680 if not HasUserGroupPermissionAnyApi(*_perms)(
1682 user=apiuser, user_group_name=user_group.users_group_name):
1681 user=apiuser, user_group_name=user_group.users_group_name):
1683 raise JSONRPCError(
1682 raise JSONRPCError(
1684 'user group `%s` does not exist' % (usergroupid,))
1683 'user group `%s` does not exist' % (usergroupid,))
1685
1684
1686 try:
1685 try:
1687 RepoModel().grant_user_group_permission(
1686 RepoModel().grant_user_group_permission(
1688 repo=repo, group_name=user_group, perm=perm)
1687 repo=repo, group_name=user_group, perm=perm)
1689
1688
1690 Session().commit()
1689 Session().commit()
1691 return {
1690 return {
1692 'msg': 'Granted perm: `%s` for user group: `%s` in '
1691 'msg': 'Granted perm: `%s` for user group: `%s` in '
1693 'repo: `%s`' % (
1692 'repo: `%s`' % (
1694 perm.permission_name, user_group.users_group_name,
1693 perm.permission_name, user_group.users_group_name,
1695 repo.repo_name
1694 repo.repo_name
1696 ),
1695 ),
1697 'success': True
1696 'success': True
1698 }
1697 }
1699 except Exception:
1698 except Exception:
1700 log.exception(
1699 log.exception(
1701 "Exception occurred while trying change permission on repo")
1700 "Exception occurred while trying change permission on repo")
1702 raise JSONRPCError(
1701 raise JSONRPCError(
1703 'failed to edit permission for user group: `%s` in '
1702 'failed to edit permission for user group: `%s` in '
1704 'repo: `%s`' % (
1703 'repo: `%s`' % (
1705 usergroupid, repo.repo_name
1704 usergroupid, repo.repo_name
1706 )
1705 )
1707 )
1706 )
1708
1707
1709
1708
1710 @jsonrpc_method()
1709 @jsonrpc_method()
1711 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1710 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1712 """
1711 """
1713 Revoke the permissions of a user group on a given repository.
1712 Revoke the permissions of a user group on a given repository.
1714
1713
1715 This command can only be run using an |authtoken| with admin
1714 This command can only be run using an |authtoken| with admin
1716 permissions on the |repo|.
1715 permissions on the |repo|.
1717
1716
1718 :param apiuser: This is filled automatically from the |authtoken|.
1717 :param apiuser: This is filled automatically from the |authtoken|.
1719 :type apiuser: AuthUser
1718 :type apiuser: AuthUser
1720 :param repoid: Set the repository name or repository ID.
1719 :param repoid: Set the repository name or repository ID.
1721 :type repoid: str or int
1720 :type repoid: str or int
1722 :param usergroupid: Specify the user group ID.
1721 :param usergroupid: Specify the user group ID.
1723 :type usergroupid: str or int
1722 :type usergroupid: str or int
1724
1723
1725 Example output:
1724 Example output:
1726
1725
1727 .. code-block:: bash
1726 .. code-block:: bash
1728
1727
1729 id : <id_given_in_input>
1728 id : <id_given_in_input>
1730 result: {
1729 result: {
1731 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1730 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1732 "success": true
1731 "success": true
1733 }
1732 }
1734 error: null
1733 error: null
1735 """
1734 """
1736
1735
1737 repo = get_repo_or_error(repoid)
1736 repo = get_repo_or_error(repoid)
1738 if not has_superadmin_permission(apiuser):
1737 if not has_superadmin_permission(apiuser):
1739 _perms = ('repository.admin',)
1738 _perms = ('repository.admin',)
1740 validate_repo_permissions(apiuser, repoid, repo, _perms)
1739 validate_repo_permissions(apiuser, repoid, repo, _perms)
1741
1740
1742 user_group = get_user_group_or_error(usergroupid)
1741 user_group = get_user_group_or_error(usergroupid)
1743 if not has_superadmin_permission(apiuser):
1742 if not has_superadmin_permission(apiuser):
1744 # check if we have at least read permission for this user group !
1743 # check if we have at least read permission for this user group !
1745 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1744 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1746 if not HasUserGroupPermissionAnyApi(*_perms)(
1745 if not HasUserGroupPermissionAnyApi(*_perms)(
1747 user=apiuser, user_group_name=user_group.users_group_name):
1746 user=apiuser, user_group_name=user_group.users_group_name):
1748 raise JSONRPCError(
1747 raise JSONRPCError(
1749 'user group `%s` does not exist' % (usergroupid,))
1748 'user group `%s` does not exist' % (usergroupid,))
1750
1749
1751 try:
1750 try:
1752 RepoModel().revoke_user_group_permission(
1751 RepoModel().revoke_user_group_permission(
1753 repo=repo, group_name=user_group)
1752 repo=repo, group_name=user_group)
1754
1753
1755 Session().commit()
1754 Session().commit()
1756 return {
1755 return {
1757 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1756 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1758 user_group.users_group_name, repo.repo_name
1757 user_group.users_group_name, repo.repo_name
1759 ),
1758 ),
1760 'success': True
1759 'success': True
1761 }
1760 }
1762 except Exception:
1761 except Exception:
1763 log.exception("Exception occurred while trying revoke "
1762 log.exception("Exception occurred while trying revoke "
1764 "user group permission on repo")
1763 "user group permission on repo")
1765 raise JSONRPCError(
1764 raise JSONRPCError(
1766 'failed to edit permission for user group: `%s` in '
1765 'failed to edit permission for user group: `%s` in '
1767 'repo: `%s`' % (
1766 'repo: `%s`' % (
1768 user_group.users_group_name, repo.repo_name
1767 user_group.users_group_name, repo.repo_name
1769 )
1768 )
1770 )
1769 )
1771
1770
1772
1771
1773 @jsonrpc_method()
1772 @jsonrpc_method()
1774 def pull(request, apiuser, repoid):
1773 def pull(request, apiuser, repoid):
1775 """
1774 """
1776 Triggers a pull on the given repository from a remote location. You
1775 Triggers a pull on the given repository from a remote location. You
1777 can use this to keep remote repositories up-to-date.
1776 can use this to keep remote repositories up-to-date.
1778
1777
1779 This command can only be run using an |authtoken| with admin
1778 This command can only be run using an |authtoken| with admin
1780 rights to the specified repository. For more information,
1779 rights to the specified repository. For more information,
1781 see :ref:`config-token-ref`.
1780 see :ref:`config-token-ref`.
1782
1781
1783 This command takes the following options:
1782 This command takes the following options:
1784
1783
1785 :param apiuser: This is filled automatically from the |authtoken|.
1784 :param apiuser: This is filled automatically from the |authtoken|.
1786 :type apiuser: AuthUser
1785 :type apiuser: AuthUser
1787 :param repoid: The repository name or repository ID.
1786 :param repoid: The repository name or repository ID.
1788 :type repoid: str or int
1787 :type repoid: str or int
1789
1788
1790 Example output:
1789 Example output:
1791
1790
1792 .. code-block:: bash
1791 .. code-block:: bash
1793
1792
1794 id : <id_given_in_input>
1793 id : <id_given_in_input>
1795 result : {
1794 result : {
1796 "msg": "Pulled from `<repository name>`"
1795 "msg": "Pulled from `<repository name>`"
1797 "repository": "<repository name>"
1796 "repository": "<repository name>"
1798 }
1797 }
1799 error : null
1798 error : null
1800
1799
1801 Example error output:
1800 Example error output:
1802
1801
1803 .. code-block:: bash
1802 .. code-block:: bash
1804
1803
1805 id : <id_given_in_input>
1804 id : <id_given_in_input>
1806 result : null
1805 result : null
1807 error : {
1806 error : {
1808 "Unable to pull changes from `<reponame>`"
1807 "Unable to pull changes from `<reponame>`"
1809 }
1808 }
1810
1809
1811 """
1810 """
1812
1811
1813 repo = get_repo_or_error(repoid)
1812 repo = get_repo_or_error(repoid)
1814 if not has_superadmin_permission(apiuser):
1813 if not has_superadmin_permission(apiuser):
1815 _perms = ('repository.admin',)
1814 _perms = ('repository.admin',)
1816 validate_repo_permissions(apiuser, repoid, repo, _perms)
1815 validate_repo_permissions(apiuser, repoid, repo, _perms)
1817
1816
1818 try:
1817 try:
1819 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1818 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1820 return {
1819 return {
1821 'msg': 'Pulled from `%s`' % repo.repo_name,
1820 'msg': 'Pulled from `%s`' % repo.repo_name,
1822 'repository': repo.repo_name
1821 'repository': repo.repo_name
1823 }
1822 }
1824 except Exception:
1823 except Exception:
1825 log.exception("Exception occurred while trying to "
1824 log.exception("Exception occurred while trying to "
1826 "pull changes from remote location")
1825 "pull changes from remote location")
1827 raise JSONRPCError(
1826 raise JSONRPCError(
1828 'Unable to pull changes from `%s`' % repo.repo_name
1827 'Unable to pull changes from `%s`' % repo.repo_name
1829 )
1828 )
1830
1829
1831
1830
1832 @jsonrpc_method()
1831 @jsonrpc_method()
1833 def strip(request, apiuser, repoid, revision, branch):
1832 def strip(request, apiuser, repoid, revision, branch):
1834 """
1833 """
1835 Strips the given revision from the specified repository.
1834 Strips the given revision from the specified repository.
1836
1835
1837 * This will remove the revision and all of its decendants.
1836 * This will remove the revision and all of its decendants.
1838
1837
1839 This command can only be run using an |authtoken| with admin rights to
1838 This command can only be run using an |authtoken| with admin rights to
1840 the specified repository.
1839 the specified repository.
1841
1840
1842 This command takes the following options:
1841 This command takes the following options:
1843
1842
1844 :param apiuser: This is filled automatically from the |authtoken|.
1843 :param apiuser: This is filled automatically from the |authtoken|.
1845 :type apiuser: AuthUser
1844 :type apiuser: AuthUser
1846 :param repoid: The repository name or repository ID.
1845 :param repoid: The repository name or repository ID.
1847 :type repoid: str or int
1846 :type repoid: str or int
1848 :param revision: The revision you wish to strip.
1847 :param revision: The revision you wish to strip.
1849 :type revision: str
1848 :type revision: str
1850 :param branch: The branch from which to strip the revision.
1849 :param branch: The branch from which to strip the revision.
1851 :type branch: str
1850 :type branch: str
1852
1851
1853 Example output:
1852 Example output:
1854
1853
1855 .. code-block:: bash
1854 .. code-block:: bash
1856
1855
1857 id : <id_given_in_input>
1856 id : <id_given_in_input>
1858 result : {
1857 result : {
1859 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1858 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1860 "repository": "<repository name>"
1859 "repository": "<repository name>"
1861 }
1860 }
1862 error : null
1861 error : null
1863
1862
1864 Example error output:
1863 Example error output:
1865
1864
1866 .. code-block:: bash
1865 .. code-block:: bash
1867
1866
1868 id : <id_given_in_input>
1867 id : <id_given_in_input>
1869 result : null
1868 result : null
1870 error : {
1869 error : {
1871 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1870 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1872 }
1871 }
1873
1872
1874 """
1873 """
1875
1874
1876 repo = get_repo_or_error(repoid)
1875 repo = get_repo_or_error(repoid)
1877 if not has_superadmin_permission(apiuser):
1876 if not has_superadmin_permission(apiuser):
1878 _perms = ('repository.admin',)
1877 _perms = ('repository.admin',)
1879 validate_repo_permissions(apiuser, repoid, repo, _perms)
1878 validate_repo_permissions(apiuser, repoid, repo, _perms)
1880
1879
1881 try:
1880 try:
1882 ScmModel().strip(repo, revision, branch)
1881 ScmModel().strip(repo, revision, branch)
1883 return {
1882 return {
1884 'msg': 'Stripped commit %s from repo `%s`' % (
1883 'msg': 'Stripped commit %s from repo `%s`' % (
1885 revision, repo.repo_name),
1884 revision, repo.repo_name),
1886 'repository': repo.repo_name
1885 'repository': repo.repo_name
1887 }
1886 }
1888 except Exception:
1887 except Exception:
1889 log.exception("Exception while trying to strip")
1888 log.exception("Exception while trying to strip")
1890 raise JSONRPCError(
1889 raise JSONRPCError(
1891 'Unable to strip commit %s from repo `%s`' % (
1890 'Unable to strip commit %s from repo `%s`' % (
1892 revision, repo.repo_name)
1891 revision, repo.repo_name)
1893 )
1892 )
1894
1893
1895
1894
1896 @jsonrpc_method()
1895 @jsonrpc_method()
1897 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1896 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1898 """
1897 """
1899 Returns all settings for a repository. If key is given it only returns the
1898 Returns all settings for a repository. If key is given it only returns the
1900 setting identified by the key or null.
1899 setting identified by the key or null.
1901
1900
1902 :param apiuser: This is filled automatically from the |authtoken|.
1901 :param apiuser: This is filled automatically from the |authtoken|.
1903 :type apiuser: AuthUser
1902 :type apiuser: AuthUser
1904 :param repoid: The repository name or repository id.
1903 :param repoid: The repository name or repository id.
1905 :type repoid: str or int
1904 :type repoid: str or int
1906 :param key: Key of the setting to return.
1905 :param key: Key of the setting to return.
1907 :type: key: Optional(str)
1906 :type: key: Optional(str)
1908
1907
1909 Example output:
1908 Example output:
1910
1909
1911 .. code-block:: bash
1910 .. code-block:: bash
1912
1911
1913 {
1912 {
1914 "error": null,
1913 "error": null,
1915 "id": 237,
1914 "id": 237,
1916 "result": {
1915 "result": {
1917 "extensions_largefiles": true,
1916 "extensions_largefiles": true,
1918 "extensions_evolve": true,
1917 "extensions_evolve": true,
1919 "hooks_changegroup_push_logger": true,
1918 "hooks_changegroup_push_logger": true,
1920 "hooks_changegroup_repo_size": false,
1919 "hooks_changegroup_repo_size": false,
1921 "hooks_outgoing_pull_logger": true,
1920 "hooks_outgoing_pull_logger": true,
1922 "phases_publish": "True",
1921 "phases_publish": "True",
1923 "rhodecode_hg_use_rebase_for_merging": true,
1922 "rhodecode_hg_use_rebase_for_merging": true,
1924 "rhodecode_pr_merge_enabled": true,
1923 "rhodecode_pr_merge_enabled": true,
1925 "rhodecode_use_outdated_comments": true
1924 "rhodecode_use_outdated_comments": true
1926 }
1925 }
1927 }
1926 }
1928 """
1927 """
1929
1928
1930 # Restrict access to this api method to admins only.
1929 # Restrict access to this api method to admins only.
1931 if not has_superadmin_permission(apiuser):
1930 if not has_superadmin_permission(apiuser):
1932 raise JSONRPCForbidden()
1931 raise JSONRPCForbidden()
1933
1932
1934 try:
1933 try:
1935 repo = get_repo_or_error(repoid)
1934 repo = get_repo_or_error(repoid)
1936 settings_model = VcsSettingsModel(repo=repo)
1935 settings_model = VcsSettingsModel(repo=repo)
1937 settings = settings_model.get_global_settings()
1936 settings = settings_model.get_global_settings()
1938 settings.update(settings_model.get_repo_settings())
1937 settings.update(settings_model.get_repo_settings())
1939
1938
1940 # If only a single setting is requested fetch it from all settings.
1939 # If only a single setting is requested fetch it from all settings.
1941 key = Optional.extract(key)
1940 key = Optional.extract(key)
1942 if key is not None:
1941 if key is not None:
1943 settings = settings.get(key, None)
1942 settings = settings.get(key, None)
1944 except Exception:
1943 except Exception:
1945 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1944 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1946 log.exception(msg)
1945 log.exception(msg)
1947 raise JSONRPCError(msg)
1946 raise JSONRPCError(msg)
1948
1947
1949 return settings
1948 return settings
1950
1949
1951
1950
1952 @jsonrpc_method()
1951 @jsonrpc_method()
1953 def set_repo_settings(request, apiuser, repoid, settings):
1952 def set_repo_settings(request, apiuser, repoid, settings):
1954 """
1953 """
1955 Update repository settings. Returns true on success.
1954 Update repository settings. Returns true on success.
1956
1955
1957 :param apiuser: This is filled automatically from the |authtoken|.
1956 :param apiuser: This is filled automatically from the |authtoken|.
1958 :type apiuser: AuthUser
1957 :type apiuser: AuthUser
1959 :param repoid: The repository name or repository id.
1958 :param repoid: The repository name or repository id.
1960 :type repoid: str or int
1959 :type repoid: str or int
1961 :param settings: The new settings for the repository.
1960 :param settings: The new settings for the repository.
1962 :type: settings: dict
1961 :type: settings: dict
1963
1962
1964 Example output:
1963 Example output:
1965
1964
1966 .. code-block:: bash
1965 .. code-block:: bash
1967
1966
1968 {
1967 {
1969 "error": null,
1968 "error": null,
1970 "id": 237,
1969 "id": 237,
1971 "result": true
1970 "result": true
1972 }
1971 }
1973 """
1972 """
1974 # Restrict access to this api method to admins only.
1973 # Restrict access to this api method to admins only.
1975 if not has_superadmin_permission(apiuser):
1974 if not has_superadmin_permission(apiuser):
1976 raise JSONRPCForbidden()
1975 raise JSONRPCForbidden()
1977
1976
1978 if type(settings) is not dict:
1977 if type(settings) is not dict:
1979 raise JSONRPCError('Settings have to be a JSON Object.')
1978 raise JSONRPCError('Settings have to be a JSON Object.')
1980
1979
1981 try:
1980 try:
1982 settings_model = VcsSettingsModel(repo=repoid)
1981 settings_model = VcsSettingsModel(repo=repoid)
1983
1982
1984 # Merge global, repo and incoming settings.
1983 # Merge global, repo and incoming settings.
1985 new_settings = settings_model.get_global_settings()
1984 new_settings = settings_model.get_global_settings()
1986 new_settings.update(settings_model.get_repo_settings())
1985 new_settings.update(settings_model.get_repo_settings())
1987 new_settings.update(settings)
1986 new_settings.update(settings)
1988
1987
1989 # Update the settings.
1988 # Update the settings.
1990 inherit_global_settings = new_settings.get(
1989 inherit_global_settings = new_settings.get(
1991 'inherit_global_settings', False)
1990 'inherit_global_settings', False)
1992 settings_model.create_or_update_repo_settings(
1991 settings_model.create_or_update_repo_settings(
1993 new_settings, inherit_global_settings=inherit_global_settings)
1992 new_settings, inherit_global_settings=inherit_global_settings)
1994 Session().commit()
1993 Session().commit()
1995 except Exception:
1994 except Exception:
1996 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1995 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1997 log.exception(msg)
1996 log.exception(msg)
1998 raise JSONRPCError(msg)
1997 raise JSONRPCError(msg)
1999
1998
2000 # Indicate success.
1999 # Indicate success.
2001 return True
2000 return True
2002
2001
2003
2002
2004 @jsonrpc_method()
2003 @jsonrpc_method()
2005 def maintenance(request, apiuser, repoid):
2004 def maintenance(request, apiuser, repoid):
2006 """
2005 """
2007 Triggers a maintenance on the given repository.
2006 Triggers a maintenance on the given repository.
2008
2007
2009 This command can only be run using an |authtoken| with admin
2008 This command can only be run using an |authtoken| with admin
2010 rights to the specified repository. For more information,
2009 rights to the specified repository. For more information,
2011 see :ref:`config-token-ref`.
2010 see :ref:`config-token-ref`.
2012
2011
2013 This command takes the following options:
2012 This command takes the following options:
2014
2013
2015 :param apiuser: This is filled automatically from the |authtoken|.
2014 :param apiuser: This is filled automatically from the |authtoken|.
2016 :type apiuser: AuthUser
2015 :type apiuser: AuthUser
2017 :param repoid: The repository name or repository ID.
2016 :param repoid: The repository name or repository ID.
2018 :type repoid: str or int
2017 :type repoid: str or int
2019
2018
2020 Example output:
2019 Example output:
2021
2020
2022 .. code-block:: bash
2021 .. code-block:: bash
2023
2022
2024 id : <id_given_in_input>
2023 id : <id_given_in_input>
2025 result : {
2024 result : {
2026 "msg": "executed maintenance command",
2025 "msg": "executed maintenance command",
2027 "executed_actions": [
2026 "executed_actions": [
2028 <action_message>, <action_message2>...
2027 <action_message>, <action_message2>...
2029 ],
2028 ],
2030 "repository": "<repository name>"
2029 "repository": "<repository name>"
2031 }
2030 }
2032 error : null
2031 error : null
2033
2032
2034 Example error output:
2033 Example error output:
2035
2034
2036 .. code-block:: bash
2035 .. code-block:: bash
2037
2036
2038 id : <id_given_in_input>
2037 id : <id_given_in_input>
2039 result : null
2038 result : null
2040 error : {
2039 error : {
2041 "Unable to execute maintenance on `<reponame>`"
2040 "Unable to execute maintenance on `<reponame>`"
2042 }
2041 }
2043
2042
2044 """
2043 """
2045
2044
2046 repo = get_repo_or_error(repoid)
2045 repo = get_repo_or_error(repoid)
2047 if not has_superadmin_permission(apiuser):
2046 if not has_superadmin_permission(apiuser):
2048 _perms = ('repository.admin',)
2047 _perms = ('repository.admin',)
2049 validate_repo_permissions(apiuser, repoid, repo, _perms)
2048 validate_repo_permissions(apiuser, repoid, repo, _perms)
2050
2049
2051 try:
2050 try:
2052 maintenance = repo_maintenance.RepoMaintenance()
2051 maintenance = repo_maintenance.RepoMaintenance()
2053 executed_actions = maintenance.execute(repo)
2052 executed_actions = maintenance.execute(repo)
2054
2053
2055 return {
2054 return {
2056 'msg': 'executed maintenance command',
2055 'msg': 'executed maintenance command',
2057 'executed_actions': executed_actions,
2056 'executed_actions': executed_actions,
2058 'repository': repo.repo_name
2057 'repository': repo.repo_name
2059 }
2058 }
2060 except Exception:
2059 except Exception:
2061 log.exception("Exception occurred while trying to run maintenance")
2060 log.exception("Exception occurred while trying to run maintenance")
2062 raise JSONRPCError(
2061 raise JSONRPCError(
2063 'Unable to execute maintenance on `%s`' % repo.repo_name)
2062 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,425 +1,426 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import collections
22 import collections
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import logging
25 import logging
26 import urlparse
26 import urlparse
27
27
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from recaptcha.client.captcha import submit
30 from recaptcha.client.captcha import submit
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.authentication.base import authenticate, HTTP_TYPE
33 from rhodecode.authentication.base import authenticate, HTTP_TYPE
34 from rhodecode.events import UserRegistered
34 from rhodecode.events import UserRegistered
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import audit_logger
36 from rhodecode.lib import audit_logger
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
38 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
39 from rhodecode.lib.base import get_ip_addr
39 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.exceptions import UserCreationError
40 from rhodecode.lib.exceptions import UserCreationError
41 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.model.db import User, UserApiKeys
42 from rhodecode.model.db import User, UserApiKeys
43 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
43 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.auth_token import AuthTokenModel
45 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.translation import _
48 from rhodecode.translation import _
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53 CaptchaData = collections.namedtuple(
53 CaptchaData = collections.namedtuple(
54 'CaptchaData', 'active, private_key, public_key')
54 'CaptchaData', 'active, private_key, public_key')
55
55
56
56
57 def _store_user_in_session(session, username, remember=False):
57 def _store_user_in_session(session, username, remember=False):
58 user = User.get_by_username(username, case_insensitive=True)
58 user = User.get_by_username(username, case_insensitive=True)
59 auth_user = AuthUser(user.user_id)
59 auth_user = AuthUser(user.user_id)
60 auth_user.set_authenticated()
60 auth_user.set_authenticated()
61 cs = auth_user.get_cookie_store()
61 cs = auth_user.get_cookie_store()
62 session['rhodecode_user'] = cs
62 session['rhodecode_user'] = cs
63 user.update_lastlogin()
63 user.update_lastlogin()
64 Session().commit()
64 Session().commit()
65
65
66 # If they want to be remembered, update the cookie
66 # If they want to be remembered, update the cookie
67 if remember:
67 if remember:
68 _year = (datetime.datetime.now() +
68 _year = (datetime.datetime.now() +
69 datetime.timedelta(seconds=60 * 60 * 24 * 365))
69 datetime.timedelta(seconds=60 * 60 * 24 * 365))
70 session._set_cookie_expires(_year)
70 session._set_cookie_expires(_year)
71
71
72 session.save()
72 session.save()
73
73
74 safe_cs = cs.copy()
74 safe_cs = cs.copy()
75 safe_cs['password'] = '****'
75 safe_cs['password'] = '****'
76 log.info('user %s is now authenticated and stored in '
76 log.info('user %s is now authenticated and stored in '
77 'session, session attrs %s', username, safe_cs)
77 'session, session attrs %s', username, safe_cs)
78
78
79 # dumps session attrs back to cookie
79 # dumps session attrs back to cookie
80 session._update_cookie_out()
80 session._update_cookie_out()
81 # we set new cookie
81 # we set new cookie
82 headers = None
82 headers = None
83 if session.request['set_cookie']:
83 if session.request['set_cookie']:
84 # send set-cookie headers back to response to update cookie
84 # send set-cookie headers back to response to update cookie
85 headers = [('Set-Cookie', session.request['cookie_out'])]
85 headers = [('Set-Cookie', session.request['cookie_out'])]
86 return headers
86 return headers
87
87
88
88
89 def get_came_from(request):
89 def get_came_from(request):
90 came_from = safe_str(request.GET.get('came_from', ''))
90 came_from = safe_str(request.GET.get('came_from', ''))
91 parsed = urlparse.urlparse(came_from)
91 parsed = urlparse.urlparse(came_from)
92 allowed_schemes = ['http', 'https']
92 allowed_schemes = ['http', 'https']
93 default_came_from = h.route_path('home')
93 default_came_from = h.route_path('home')
94 if parsed.scheme and parsed.scheme not in allowed_schemes:
94 if parsed.scheme and parsed.scheme not in allowed_schemes:
95 log.error('Suspicious URL scheme detected %s for url %s' %
95 log.error('Suspicious URL scheme detected %s for url %s' %
96 (parsed.scheme, parsed))
96 (parsed.scheme, parsed))
97 came_from = default_came_from
97 came_from = default_came_from
98 elif parsed.netloc and request.host != parsed.netloc:
98 elif parsed.netloc and request.host != parsed.netloc:
99 log.error('Suspicious NETLOC detected %s for url %s server url '
99 log.error('Suspicious NETLOC detected %s for url %s server url '
100 'is: %s' % (parsed.netloc, parsed, request.host))
100 'is: %s' % (parsed.netloc, parsed, request.host))
101 came_from = default_came_from
101 came_from = default_came_from
102 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
102 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
103 log.error('Header injection detected `%s` for url %s server url ' %
103 log.error('Header injection detected `%s` for url %s server url ' %
104 (parsed.path, parsed))
104 (parsed.path, parsed))
105 came_from = default_came_from
105 came_from = default_came_from
106
106
107 return came_from or default_came_from
107 return came_from or default_came_from
108
108
109
109
110 class LoginView(BaseAppView):
110 class LoginView(BaseAppView):
111
111
112 def load_default_context(self):
112 def load_default_context(self):
113 c = self._get_local_tmpl_context()
113 c = self._get_local_tmpl_context()
114 c.came_from = get_came_from(self.request)
114 c.came_from = get_came_from(self.request)
115 self._register_global_c(c)
115 self._register_global_c(c)
116 return c
116 return c
117
117
118 def _get_captcha_data(self):
118 def _get_captcha_data(self):
119 settings = SettingsModel().get_all_settings()
119 settings = SettingsModel().get_all_settings()
120 private_key = settings.get('rhodecode_captcha_private_key')
120 private_key = settings.get('rhodecode_captcha_private_key')
121 public_key = settings.get('rhodecode_captcha_public_key')
121 public_key = settings.get('rhodecode_captcha_public_key')
122 active = bool(private_key)
122 active = bool(private_key)
123 return CaptchaData(
123 return CaptchaData(
124 active=active, private_key=private_key, public_key=public_key)
124 active=active, private_key=private_key, public_key=public_key)
125
125
126 @view_config(
126 @view_config(
127 route_name='login', request_method='GET',
127 route_name='login', request_method='GET',
128 renderer='rhodecode:templates/login.mako')
128 renderer='rhodecode:templates/login.mako')
129 def login(self):
129 def login(self):
130 c = self.load_default_context()
130 c = self.load_default_context()
131 auth_user = self._rhodecode_user
131 auth_user = self._rhodecode_user
132
132
133 # redirect if already logged in
133 # redirect if already logged in
134 if (auth_user.is_authenticated and
134 if (auth_user.is_authenticated and
135 not auth_user.is_default and auth_user.ip_allowed):
135 not auth_user.is_default and auth_user.ip_allowed):
136 raise HTTPFound(c.came_from)
136 raise HTTPFound(c.came_from)
137
137
138 # check if we use headers plugin, and try to login using it.
138 # check if we use headers plugin, and try to login using it.
139 try:
139 try:
140 log.debug('Running PRE-AUTH for headers based authentication')
140 log.debug('Running PRE-AUTH for headers based authentication')
141 auth_info = authenticate(
141 auth_info = authenticate(
142 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
142 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
143 if auth_info:
143 if auth_info:
144 headers = _store_user_in_session(
144 headers = _store_user_in_session(
145 self.session, auth_info.get('username'))
145 self.session, auth_info.get('username'))
146 raise HTTPFound(c.came_from, headers=headers)
146 raise HTTPFound(c.came_from, headers=headers)
147 except UserCreationError as e:
147 except UserCreationError as e:
148 log.error(e)
148 log.error(e)
149 self.session.flash(e, queue='error')
149 self.session.flash(e, queue='error')
150
150
151 return self._get_template_context(c)
151 return self._get_template_context(c)
152
152
153 @view_config(
153 @view_config(
154 route_name='login', request_method='POST',
154 route_name='login', request_method='POST',
155 renderer='rhodecode:templates/login.mako')
155 renderer='rhodecode:templates/login.mako')
156 def login_post(self):
156 def login_post(self):
157 c = self.load_default_context()
157 c = self.load_default_context()
158
158
159 login_form = LoginForm()()
159 login_form = LoginForm()()
160
160
161 try:
161 try:
162 self.session.invalidate()
162 self.session.invalidate()
163 form_result = login_form.to_python(self.request.params)
163 form_result = login_form.to_python(self.request.params)
164 # form checks for username/password, now we're authenticated
164 # form checks for username/password, now we're authenticated
165 headers = _store_user_in_session(
165 headers = _store_user_in_session(
166 self.session,
166 self.session,
167 username=form_result['username'],
167 username=form_result['username'],
168 remember=form_result['remember'])
168 remember=form_result['remember'])
169 log.debug('Redirecting to "%s" after login.', c.came_from)
169 log.debug('Redirecting to "%s" after login.', c.came_from)
170
170
171 audit_user = audit_logger.UserWrap(
171 audit_user = audit_logger.UserWrap(
172 username=self.request.params.get('username'),
172 username=self.request.params.get('username'),
173 ip_addr=self.request.remote_addr)
173 ip_addr=self.request.remote_addr)
174 action_data = {'user_agent': self.request.user_agent}
174 action_data = {'user_agent': self.request.user_agent}
175 audit_logger.store(
175 audit_logger.store_web(
176 action='user.login.success', action_data=action_data,
176 action='user.login.success', action_data=action_data,
177 user=audit_user, commit=True)
177 user=audit_user, commit=True)
178
178
179 raise HTTPFound(c.came_from, headers=headers)
179 raise HTTPFound(c.came_from, headers=headers)
180 except formencode.Invalid as errors:
180 except formencode.Invalid as errors:
181 defaults = errors.value
181 defaults = errors.value
182 # remove password from filling in form again
182 # remove password from filling in form again
183 defaults.pop('password', None)
183 defaults.pop('password', None)
184 render_ctx = self._get_template_context(c)
184 render_ctx = self._get_template_context(c)
185 render_ctx.update({
185 render_ctx.update({
186 'errors': errors.error_dict,
186 'errors': errors.error_dict,
187 'defaults': defaults,
187 'defaults': defaults,
188 })
188 })
189
189
190 audit_user = audit_logger.UserWrap(
190 audit_user = audit_logger.UserWrap(
191 username=self.request.params.get('username'),
191 username=self.request.params.get('username'),
192 ip_addr=self.request.remote_addr)
192 ip_addr=self.request.remote_addr)
193 action_data = {'user_agent': self.request.user_agent}
193 action_data = {'user_agent': self.request.user_agent}
194 audit_logger.store(
194 audit_logger.store_web(
195 action='user.login.failure', action_data=action_data,
195 action='user.login.failure', action_data=action_data,
196 user=audit_user, commit=True)
196 user=audit_user, commit=True)
197 return render_ctx
197 return render_ctx
198
198
199 except UserCreationError as e:
199 except UserCreationError as e:
200 # headers auth or other auth functions that create users on
200 # headers auth or other auth functions that create users on
201 # the fly can throw this exception signaling that there's issue
201 # the fly can throw this exception signaling that there's issue
202 # with user creation, explanation should be provided in
202 # with user creation, explanation should be provided in
203 # Exception itself
203 # Exception itself
204 self.session.flash(e, queue='error')
204 self.session.flash(e, queue='error')
205 return self._get_template_context(c)
205 return self._get_template_context(c)
206
206
207 @CSRFRequired()
207 @CSRFRequired()
208 @view_config(route_name='logout', request_method='POST')
208 @view_config(route_name='logout', request_method='POST')
209 def logout(self):
209 def logout(self):
210 auth_user = self._rhodecode_user
210 auth_user = self._rhodecode_user
211 log.info('Deleting session for user: `%s`', auth_user)
211 log.info('Deleting session for user: `%s`', auth_user)
212
212
213 action_data = {'user_agent': self.request.user_agent}
213 action_data = {'user_agent': self.request.user_agent}
214 audit_logger.store(
214 audit_logger.store_web(
215 action='user.logout', action_data=action_data,
215 action='user.logout', action_data=action_data,
216 user=auth_user, commit=True)
216 user=auth_user, commit=True)
217 self.session.delete()
217 self.session.delete()
218 return HTTPFound(h.route_path('home'))
218 return HTTPFound(h.route_path('home'))
219
219
220 @HasPermissionAnyDecorator(
220 @HasPermissionAnyDecorator(
221 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
221 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
222 @view_config(
222 @view_config(
223 route_name='register', request_method='GET',
223 route_name='register', request_method='GET',
224 renderer='rhodecode:templates/register.mako',)
224 renderer='rhodecode:templates/register.mako',)
225 def register(self, defaults=None, errors=None):
225 def register(self, defaults=None, errors=None):
226 c = self.load_default_context()
226 c = self.load_default_context()
227 defaults = defaults or {}
227 defaults = defaults or {}
228 errors = errors or {}
228 errors = errors or {}
229
229
230 settings = SettingsModel().get_all_settings()
230 settings = SettingsModel().get_all_settings()
231 register_message = settings.get('rhodecode_register_message') or ''
231 register_message = settings.get('rhodecode_register_message') or ''
232 captcha = self._get_captcha_data()
232 captcha = self._get_captcha_data()
233 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
233 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
234 .AuthUser.permissions['global']
234 .AuthUser.permissions['global']
235
235
236 render_ctx = self._get_template_context(c)
236 render_ctx = self._get_template_context(c)
237 render_ctx.update({
237 render_ctx.update({
238 'defaults': defaults,
238 'defaults': defaults,
239 'errors': errors,
239 'errors': errors,
240 'auto_active': auto_active,
240 'auto_active': auto_active,
241 'captcha_active': captcha.active,
241 'captcha_active': captcha.active,
242 'captcha_public_key': captcha.public_key,
242 'captcha_public_key': captcha.public_key,
243 'register_message': register_message,
243 'register_message': register_message,
244 })
244 })
245 return render_ctx
245 return render_ctx
246
246
247 @HasPermissionAnyDecorator(
247 @HasPermissionAnyDecorator(
248 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
248 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
249 @view_config(
249 @view_config(
250 route_name='register', request_method='POST',
250 route_name='register', request_method='POST',
251 renderer='rhodecode:templates/register.mako')
251 renderer='rhodecode:templates/register.mako')
252 def register_post(self):
252 def register_post(self):
253 captcha = self._get_captcha_data()
253 captcha = self._get_captcha_data()
254 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
254 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
255 .AuthUser.permissions['global']
255 .AuthUser.permissions['global']
256
256
257 register_form = RegisterForm()()
257 register_form = RegisterForm()()
258 try:
258 try:
259 form_result = register_form.to_python(self.request.params)
259 form_result = register_form.to_python(self.request.params)
260 form_result['active'] = auto_active
260 form_result['active'] = auto_active
261
261
262 if captcha.active:
262 if captcha.active:
263 response = submit(
263 response = submit(
264 self.request.params.get('recaptcha_challenge_field'),
264 self.request.params.get('recaptcha_challenge_field'),
265 self.request.params.get('recaptcha_response_field'),
265 self.request.params.get('recaptcha_response_field'),
266 private_key=captcha.private_key,
266 private_key=captcha.private_key,
267 remoteip=get_ip_addr(self.request.environ))
267 remoteip=get_ip_addr(self.request.environ))
268 if not response.is_valid:
268 if not response.is_valid:
269 _value = form_result
269 _value = form_result
270 _msg = _('Bad captcha')
270 _msg = _('Bad captcha')
271 error_dict = {'recaptcha_field': _msg}
271 error_dict = {'recaptcha_field': _msg}
272 raise formencode.Invalid(_msg, _value, None,
272 raise formencode.Invalid(_msg, _value, None,
273 error_dict=error_dict)
273 error_dict=error_dict)
274
274
275 new_user = UserModel().create_registration(form_result)
275 new_user = UserModel().create_registration(form_result)
276 event = UserRegistered(user=new_user, session=self.session)
276 event = UserRegistered(user=new_user, session=self.session)
277 self.request.registry.notify(event)
277 self.request.registry.notify(event)
278 self.session.flash(
278 self.session.flash(
279 _('You have successfully registered with RhodeCode'),
279 _('You have successfully registered with RhodeCode'),
280 queue='success')
280 queue='success')
281 Session().commit()
281 Session().commit()
282
282
283 redirect_ro = self.request.route_path('login')
283 redirect_ro = self.request.route_path('login')
284 raise HTTPFound(redirect_ro)
284 raise HTTPFound(redirect_ro)
285
285
286 except formencode.Invalid as errors:
286 except formencode.Invalid as errors:
287 errors.value.pop('password', None)
287 errors.value.pop('password', None)
288 errors.value.pop('password_confirmation', None)
288 errors.value.pop('password_confirmation', None)
289 return self.register(
289 return self.register(
290 defaults=errors.value, errors=errors.error_dict)
290 defaults=errors.value, errors=errors.error_dict)
291
291
292 except UserCreationError as e:
292 except UserCreationError as e:
293 # container auth or other auth functions that create users on
293 # container auth or other auth functions that create users on
294 # the fly can throw this exception signaling that there's issue
294 # the fly can throw this exception signaling that there's issue
295 # with user creation, explanation should be provided in
295 # with user creation, explanation should be provided in
296 # Exception itself
296 # Exception itself
297 self.session.flash(e, queue='error')
297 self.session.flash(e, queue='error')
298 return self.register()
298 return self.register()
299
299
300 @view_config(
300 @view_config(
301 route_name='reset_password', request_method=('GET', 'POST'),
301 route_name='reset_password', request_method=('GET', 'POST'),
302 renderer='rhodecode:templates/password_reset.mako')
302 renderer='rhodecode:templates/password_reset.mako')
303 def password_reset(self):
303 def password_reset(self):
304 captcha = self._get_captcha_data()
304 captcha = self._get_captcha_data()
305
305
306 render_ctx = {
306 render_ctx = {
307 'captcha_active': captcha.active,
307 'captcha_active': captcha.active,
308 'captcha_public_key': captcha.public_key,
308 'captcha_public_key': captcha.public_key,
309 'defaults': {},
309 'defaults': {},
310 'errors': {},
310 'errors': {},
311 }
311 }
312
312
313 # always send implicit message to prevent from discovery of
313 # always send implicit message to prevent from discovery of
314 # matching emails
314 # matching emails
315 msg = _('If such email exists, a password reset link was sent to it.')
315 msg = _('If such email exists, a password reset link was sent to it.')
316
316
317 if self.request.POST:
317 if self.request.POST:
318 if h.HasPermissionAny('hg.password_reset.disabled')():
318 if h.HasPermissionAny('hg.password_reset.disabled')():
319 _email = self.request.POST.get('email', '')
319 _email = self.request.POST.get('email', '')
320 log.error('Failed attempt to reset password for `%s`.', _email)
320 log.error('Failed attempt to reset password for `%s`.', _email)
321 self.session.flash(_('Password reset has been disabled.'),
321 self.session.flash(_('Password reset has been disabled.'),
322 queue='error')
322 queue='error')
323 return HTTPFound(self.request.route_path('reset_password'))
323 return HTTPFound(self.request.route_path('reset_password'))
324
324
325 password_reset_form = PasswordResetForm()()
325 password_reset_form = PasswordResetForm()()
326 try:
326 try:
327 form_result = password_reset_form.to_python(
327 form_result = password_reset_form.to_python(
328 self.request.params)
328 self.request.params)
329 user_email = form_result['email']
329 user_email = form_result['email']
330
330
331 if captcha.active:
331 if captcha.active:
332 response = submit(
332 response = submit(
333 self.request.params.get('recaptcha_challenge_field'),
333 self.request.params.get('recaptcha_challenge_field'),
334 self.request.params.get('recaptcha_response_field'),
334 self.request.params.get('recaptcha_response_field'),
335 private_key=captcha.private_key,
335 private_key=captcha.private_key,
336 remoteip=get_ip_addr(self.request.environ))
336 remoteip=get_ip_addr(self.request.environ))
337 if not response.is_valid:
337 if not response.is_valid:
338 _value = form_result
338 _value = form_result
339 _msg = _('Bad captcha')
339 _msg = _('Bad captcha')
340 error_dict = {'recaptcha_field': _msg}
340 error_dict = {'recaptcha_field': _msg}
341 raise formencode.Invalid(
341 raise formencode.Invalid(
342 _msg, _value, None, error_dict=error_dict)
342 _msg, _value, None, error_dict=error_dict)
343
343
344 # Generate reset URL and send mail.
344 # Generate reset URL and send mail.
345 user = User.get_by_email(user_email)
345 user = User.get_by_email(user_email)
346
346
347 # generate password reset token that expires in 10minutes
347 # generate password reset token that expires in 10minutes
348 desc = 'Generated token for password reset from {}'.format(
348 desc = 'Generated token for password reset from {}'.format(
349 datetime.datetime.now().isoformat())
349 datetime.datetime.now().isoformat())
350 reset_token = AuthTokenModel().create(
350 reset_token = AuthTokenModel().create(
351 user, lifetime=10,
351 user, lifetime=10,
352 description=desc,
352 description=desc,
353 role=UserApiKeys.ROLE_PASSWORD_RESET)
353 role=UserApiKeys.ROLE_PASSWORD_RESET)
354 Session().commit()
354 Session().commit()
355
355
356 log.debug('Successfully created password recovery token')
356 log.debug('Successfully created password recovery token')
357 password_reset_url = self.request.route_url(
357 password_reset_url = self.request.route_url(
358 'reset_password_confirmation',
358 'reset_password_confirmation',
359 _query={'key': reset_token.api_key})
359 _query={'key': reset_token.api_key})
360 UserModel().reset_password_link(
360 UserModel().reset_password_link(
361 form_result, password_reset_url)
361 form_result, password_reset_url)
362 # Display success message and redirect.
362 # Display success message and redirect.
363 self.session.flash(msg, queue='success')
363 self.session.flash(msg, queue='success')
364
364
365 action_data = {'email': user_email,
365 action_data = {'email': user_email,
366 'user_agent': self.request.user_agent}
366 'user_agent': self.request.user_agent}
367 audit_logger.store(action='user.password.reset_request',
367 audit_logger.store_web(
368 action='user.password.reset_request',
368 action_data=action_data,
369 action_data=action_data,
369 user=self._rhodecode_user, commit=True)
370 user=self._rhodecode_user, commit=True)
370 return HTTPFound(self.request.route_path('reset_password'))
371 return HTTPFound(self.request.route_path('reset_password'))
371
372
372 except formencode.Invalid as errors:
373 except formencode.Invalid as errors:
373 render_ctx.update({
374 render_ctx.update({
374 'defaults': errors.value,
375 'defaults': errors.value,
375 'errors': errors.error_dict,
376 'errors': errors.error_dict,
376 })
377 })
377 if not self.request.params.get('email'):
378 if not self.request.params.get('email'):
378 # case of empty email, we want to report that
379 # case of empty email, we want to report that
379 return render_ctx
380 return render_ctx
380
381
381 if 'recaptcha_field' in errors.error_dict:
382 if 'recaptcha_field' in errors.error_dict:
382 # case of failed captcha
383 # case of failed captcha
383 return render_ctx
384 return render_ctx
384
385
385 log.debug('faking response on invalid password reset')
386 log.debug('faking response on invalid password reset')
386 # make this take 2s, to prevent brute forcing.
387 # make this take 2s, to prevent brute forcing.
387 time.sleep(2)
388 time.sleep(2)
388 self.session.flash(msg, queue='success')
389 self.session.flash(msg, queue='success')
389 return HTTPFound(self.request.route_path('reset_password'))
390 return HTTPFound(self.request.route_path('reset_password'))
390
391
391 return render_ctx
392 return render_ctx
392
393
393 @view_config(route_name='reset_password_confirmation',
394 @view_config(route_name='reset_password_confirmation',
394 request_method='GET')
395 request_method='GET')
395 def password_reset_confirmation(self):
396 def password_reset_confirmation(self):
396
397
397 if self.request.GET and self.request.GET.get('key'):
398 if self.request.GET and self.request.GET.get('key'):
398 # make this take 2s, to prevent brute forcing.
399 # make this take 2s, to prevent brute forcing.
399 time.sleep(2)
400 time.sleep(2)
400
401
401 token = AuthTokenModel().get_auth_token(
402 token = AuthTokenModel().get_auth_token(
402 self.request.GET.get('key'))
403 self.request.GET.get('key'))
403
404
404 # verify token is the correct role
405 # verify token is the correct role
405 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
406 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
406 log.debug('Got token with role:%s expected is %s',
407 log.debug('Got token with role:%s expected is %s',
407 getattr(token, 'role', 'EMPTY_TOKEN'),
408 getattr(token, 'role', 'EMPTY_TOKEN'),
408 UserApiKeys.ROLE_PASSWORD_RESET)
409 UserApiKeys.ROLE_PASSWORD_RESET)
409 self.session.flash(
410 self.session.flash(
410 _('Given reset token is invalid'), queue='error')
411 _('Given reset token is invalid'), queue='error')
411 return HTTPFound(self.request.route_path('reset_password'))
412 return HTTPFound(self.request.route_path('reset_password'))
412
413
413 try:
414 try:
414 owner = token.user
415 owner = token.user
415 data = {'email': owner.email, 'token': token.api_key}
416 data = {'email': owner.email, 'token': token.api_key}
416 UserModel().reset_password(data)
417 UserModel().reset_password(data)
417 self.session.flash(
418 self.session.flash(
418 _('Your password reset was successful, '
419 _('Your password reset was successful, '
419 'a new password has been sent to your email'),
420 'a new password has been sent to your email'),
420 queue='success')
421 queue='success')
421 except Exception as e:
422 except Exception as e:
422 log.error(e)
423 log.error(e)
423 return HTTPFound(self.request.route_path('reset_password'))
424 return HTTPFound(self.request.route_path('reset_password'))
424
425
425 return HTTPFound(self.request.route_path('login'))
426 return HTTPFound(self.request.route_path('login'))
@@ -1,98 +1,98 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import deform
23 import deform
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
32 LoginRequired, HasRepoPermissionAnyDecorator,
33 HasRepoPermissionAllDecorator, CSRFRequired)
33 HasRepoPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.forms import RepoPermsForm
35 from rhodecode.model.forms import RepoPermsForm
36 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.scm import RepoGroupList, ScmModel
38 from rhodecode.model.scm import RepoGroupList, ScmModel
39 from rhodecode.model.validation_schema.schemas import repo_schema
39 from rhodecode.model.validation_schema.schemas import repo_schema
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class RepoSettingsPermissionsView(RepoAppView):
44 class RepoSettingsPermissionsView(RepoAppView):
45
45
46 def load_default_context(self):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
48
48
49 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
50 c.repo_info = self.db_repo
50 c.repo_info = self.db_repo
51
51
52 self._register_global_c(c)
52 self._register_global_c(c)
53 return c
53 return c
54
54
55 @LoginRequired()
55 @LoginRequired()
56 @HasRepoPermissionAnyDecorator('repository.admin')
56 @HasRepoPermissionAnyDecorator('repository.admin')
57 @view_config(
57 @view_config(
58 route_name='edit_repo_perms', request_method='GET',
58 route_name='edit_repo_perms', request_method='GET',
59 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
59 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
60 def edit_permissions(self):
60 def edit_permissions(self):
61 c = self.load_default_context()
61 c = self.load_default_context()
62 c.active = 'permissions'
62 c.active = 'permissions'
63 return self._get_template_context(c)
63 return self._get_template_context(c)
64
64
65 @LoginRequired()
65 @LoginRequired()
66 @HasRepoPermissionAllDecorator('repository.admin')
66 @HasRepoPermissionAllDecorator('repository.admin')
67 @CSRFRequired()
67 @CSRFRequired()
68 @view_config(
68 @view_config(
69 route_name='edit_repo_perms', request_method='POST',
69 route_name='edit_repo_perms', request_method='POST',
70 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
70 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
71 def edit_permissions_update(self):
71 def edit_permissions_update(self):
72 _ = self.request.translate
72 _ = self.request.translate
73 c = self.load_default_context()
73 c = self.load_default_context()
74 c.active = 'permissions'
74 c.active = 'permissions'
75 data = self.request.POST
75 data = self.request.POST
76 # store private flag outside of HTML to verify if we can modify
76 # store private flag outside of HTML to verify if we can modify
77 # default user permissions, prevents submition of FAKE post data
77 # default user permissions, prevents submition of FAKE post data
78 # into the form for private repos
78 # into the form for private repos
79 data['repo_private'] = self.db_repo.private
79 data['repo_private'] = self.db_repo.private
80 form = RepoPermsForm()().to_python(data)
80 form = RepoPermsForm()().to_python(data)
81 changes = RepoModel().update_permissions(
81 changes = RepoModel().update_permissions(
82 self.db_repo_name, form['perm_additions'], form['perm_updates'],
82 self.db_repo_name, form['perm_additions'], form['perm_updates'],
83 form['perm_deletions'])
83 form['perm_deletions'])
84
84
85 action_data = {
85 action_data = {
86 'added': changes['added'],
86 'added': changes['added'],
87 'updated': changes['updated'],
87 'updated': changes['updated'],
88 'deleted': changes['deleted'],
88 'deleted': changes['deleted'],
89 }
89 }
90 audit_logger.store(
90 audit_logger.store_web(
91 'repo.edit.permissions', action_data=action_data,
91 'repo.edit.permissions', action_data=action_data,
92 user=self._rhodecode_user, repo=self.db_repo)
92 user=self._rhodecode_user, repo=self.db_repo)
93
93
94 Session().commit()
94 Session().commit()
95 h.flash(_('Repository permissions updated'), category='success')
95 h.flash(_('Repository permissions updated'), category='success')
96
96
97 raise HTTPFound(
97 raise HTTPFound(
98 self.request.route_path('edit_repo_perms', repo_name=self.db_repo_name))
98 self.request.route_path('edit_repo_perms', repo_name=self.db_repo_name))
@@ -1,179 +1,179 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import deform
23 import deform
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
32 LoginRequired, HasRepoPermissionAnyDecorator,
33 HasRepoPermissionAllDecorator, CSRFRequired)
33 HasRepoPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.scm import RepoGroupList, ScmModel
37 from rhodecode.model.scm import RepoGroupList, ScmModel
38 from rhodecode.model.validation_schema.schemas import repo_schema
38 from rhodecode.model.validation_schema.schemas import repo_schema
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class RepoSettingsView(RepoAppView):
43 class RepoSettingsView(RepoAppView):
44
44
45 def load_default_context(self):
45 def load_default_context(self):
46 c = self._get_local_tmpl_context()
46 c = self._get_local_tmpl_context()
47
47
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 c.repo_info = self.db_repo
49 c.repo_info = self.db_repo
50
50
51 acl_groups = RepoGroupList(
51 acl_groups = RepoGroupList(
52 RepoGroup.query().all(),
52 RepoGroup.query().all(),
53 perm_set=['group.write', 'group.admin'])
53 perm_set=['group.write', 'group.admin'])
54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
55 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
55 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
56
56
57 # in case someone no longer have a group.write access to a repository
57 # in case someone no longer have a group.write access to a repository
58 # pre fill the list with this entry, we don't care if this is the same
58 # pre fill the list with this entry, we don't care if this is the same
59 # but it will allow saving repo data properly.
59 # but it will allow saving repo data properly.
60 repo_group = self.db_repo.group
60 repo_group = self.db_repo.group
61 if repo_group and repo_group.group_id not in c.repo_groups_choices:
61 if repo_group and repo_group.group_id not in c.repo_groups_choices:
62 c.repo_groups_choices.append(repo_group.group_id)
62 c.repo_groups_choices.append(repo_group.group_id)
63 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
63 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
64
64
65 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
65 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
66 # we might be in missing requirement state, so we load things
66 # we might be in missing requirement state, so we load things
67 # without touching scm_instance()
67 # without touching scm_instance()
68 c.landing_revs_choices, c.landing_revs = \
68 c.landing_revs_choices, c.landing_revs = \
69 ScmModel().get_repo_landing_revs()
69 ScmModel().get_repo_landing_revs()
70 else:
70 else:
71 c.landing_revs_choices, c.landing_revs = \
71 c.landing_revs_choices, c.landing_revs = \
72 ScmModel().get_repo_landing_revs(self.db_repo)
72 ScmModel().get_repo_landing_revs(self.db_repo)
73
73
74 c.personal_repo_group = c.auth_user.personal_repo_group
74 c.personal_repo_group = c.auth_user.personal_repo_group
75 c.repo_fields = RepositoryField.query()\
75 c.repo_fields = RepositoryField.query()\
76 .filter(RepositoryField.repository == self.db_repo).all()
76 .filter(RepositoryField.repository == self.db_repo).all()
77
77
78 self._register_global_c(c)
78 self._register_global_c(c)
79 return c
79 return c
80
80
81 def _get_schema(self, c, old_values=None):
81 def _get_schema(self, c, old_values=None):
82 return repo_schema.RepoSettingsSchema().bind(
82 return repo_schema.RepoSettingsSchema().bind(
83 repo_type=self.db_repo.repo_type,
83 repo_type=self.db_repo.repo_type,
84 repo_type_options=[self.db_repo.repo_type],
84 repo_type_options=[self.db_repo.repo_type],
85 repo_ref_options=c.landing_revs_choices,
85 repo_ref_options=c.landing_revs_choices,
86 repo_ref_items=c.landing_revs,
86 repo_ref_items=c.landing_revs,
87 repo_repo_group_options=c.repo_groups_choices,
87 repo_repo_group_options=c.repo_groups_choices,
88 repo_repo_group_items=c.repo_groups,
88 repo_repo_group_items=c.repo_groups,
89 # user caller
89 # user caller
90 user=self._rhodecode_user,
90 user=self._rhodecode_user,
91 old_values=old_values
91 old_values=old_values
92 )
92 )
93
93
94 @LoginRequired()
94 @LoginRequired()
95 @HasRepoPermissionAnyDecorator('repository.admin')
95 @HasRepoPermissionAnyDecorator('repository.admin')
96 @view_config(
96 @view_config(
97 route_name='edit_repo', request_method='GET',
97 route_name='edit_repo', request_method='GET',
98 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
98 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
99 def edit_settings(self):
99 def edit_settings(self):
100 c = self.load_default_context()
100 c = self.load_default_context()
101 c.active = 'settings'
101 c.active = 'settings'
102
102
103 defaults = RepoModel()._get_defaults(self.db_repo_name)
103 defaults = RepoModel()._get_defaults(self.db_repo_name)
104 defaults['repo_owner'] = defaults['user']
104 defaults['repo_owner'] = defaults['user']
105 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
105 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
106
106
107 schema = self._get_schema(c)
107 schema = self._get_schema(c)
108 c.form = RcForm(schema, appstruct=defaults)
108 c.form = RcForm(schema, appstruct=defaults)
109 return self._get_template_context(c)
109 return self._get_template_context(c)
110
110
111 @LoginRequired()
111 @LoginRequired()
112 @HasRepoPermissionAllDecorator('repository.admin')
112 @HasRepoPermissionAllDecorator('repository.admin')
113 @CSRFRequired()
113 @CSRFRequired()
114 @view_config(
114 @view_config(
115 route_name='edit_repo', request_method='POST',
115 route_name='edit_repo', request_method='POST',
116 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
116 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
117 def edit_settings_update(self):
117 def edit_settings_update(self):
118 _ = self.request.translate
118 _ = self.request.translate
119 c = self.load_default_context()
119 c = self.load_default_context()
120 c.active = 'settings'
120 c.active = 'settings'
121 old_repo_name = self.db_repo_name
121 old_repo_name = self.db_repo_name
122
122
123 old_values = self.db_repo.get_api_data()
123 old_values = self.db_repo.get_api_data()
124 schema = self._get_schema(c, old_values=old_values)
124 schema = self._get_schema(c, old_values=old_values)
125
125
126 c.form = RcForm(schema)
126 c.form = RcForm(schema)
127 pstruct = self.request.POST.items()
127 pstruct = self.request.POST.items()
128 pstruct.append(('repo_type', self.db_repo.repo_type))
128 pstruct.append(('repo_type', self.db_repo.repo_type))
129 try:
129 try:
130 schema_data = c.form.validate(pstruct)
130 schema_data = c.form.validate(pstruct)
131 except deform.ValidationFailure as err_form:
131 except deform.ValidationFailure as err_form:
132 return self._get_template_context(c)
132 return self._get_template_context(c)
133
133
134 # data is now VALID, proceed with updates
134 # data is now VALID, proceed with updates
135 # save validated data back into the updates dict
135 # save validated data back into the updates dict
136 validated_updates = dict(
136 validated_updates = dict(
137 repo_name=schema_data['repo_group']['repo_name_without_group'],
137 repo_name=schema_data['repo_group']['repo_name_without_group'],
138 repo_group=schema_data['repo_group']['repo_group_id'],
138 repo_group=schema_data['repo_group']['repo_group_id'],
139
139
140 user=schema_data['repo_owner'],
140 user=schema_data['repo_owner'],
141 repo_description=schema_data['repo_description'],
141 repo_description=schema_data['repo_description'],
142 repo_private=schema_data['repo_private'],
142 repo_private=schema_data['repo_private'],
143 clone_uri=schema_data['repo_clone_uri'],
143 clone_uri=schema_data['repo_clone_uri'],
144 repo_landing_rev=schema_data['repo_landing_commit_ref'],
144 repo_landing_rev=schema_data['repo_landing_commit_ref'],
145 repo_enable_statistics=schema_data['repo_enable_statistics'],
145 repo_enable_statistics=schema_data['repo_enable_statistics'],
146 repo_enable_locking=schema_data['repo_enable_locking'],
146 repo_enable_locking=schema_data['repo_enable_locking'],
147 repo_enable_downloads=schema_data['repo_enable_downloads'],
147 repo_enable_downloads=schema_data['repo_enable_downloads'],
148 )
148 )
149 # detect if CLONE URI changed, if we get OLD means we keep old values
149 # detect if CLONE URI changed, if we get OLD means we keep old values
150 if schema_data['repo_clone_uri_change'] == 'OLD':
150 if schema_data['repo_clone_uri_change'] == 'OLD':
151 validated_updates['clone_uri'] = self.db_repo.clone_uri
151 validated_updates['clone_uri'] = self.db_repo.clone_uri
152
152
153 # use the new full name for redirect
153 # use the new full name for redirect
154 new_repo_name = schema_data['repo_group']['repo_name_with_group']
154 new_repo_name = schema_data['repo_group']['repo_name_with_group']
155
155
156 # save extra fields into our validated data
156 # save extra fields into our validated data
157 for key, value in pstruct:
157 for key, value in pstruct:
158 if key.startswith(RepositoryField.PREFIX):
158 if key.startswith(RepositoryField.PREFIX):
159 validated_updates[key] = value
159 validated_updates[key] = value
160
160
161 try:
161 try:
162 RepoModel().update(self.db_repo, **validated_updates)
162 RepoModel().update(self.db_repo, **validated_updates)
163 ScmModel().mark_for_invalidation(new_repo_name)
163 ScmModel().mark_for_invalidation(new_repo_name)
164
164
165 audit_logger.store(
165 audit_logger.store_web(
166 'repo.edit', action_data={'old_data': old_values},
166 'repo.edit', action_data={'old_data': old_values},
167 user=self._rhodecode_user, repo=self.db_repo)
167 user=self._rhodecode_user, repo=self.db_repo)
168
168
169 Session().commit()
169 Session().commit()
170
170
171 h.flash(_('Repository {} updated successfully').format(
171 h.flash(_('Repository {} updated successfully').format(
172 old_repo_name), category='success')
172 old_repo_name), category='success')
173 except Exception:
173 except Exception:
174 log.exception("Exception during update of repository")
174 log.exception("Exception during update of repository")
175 h.flash(_('Error occurred during update of repository {}').format(
175 h.flash(_('Error occurred during update of repository {}').format(
176 old_repo_name), category='error')
176 old_repo_name), category='error')
177
177
178 raise HTTPFound(
178 raise HTTPFound(
179 self.request.route_path('edit_repo', repo_name=new_repo_name))
179 self.request.route_path('edit_repo', repo_name=new_repo_name))
@@ -1,227 +1,226 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.lib.exceptions import AttachedForksError
31 from rhodecode.lib.exceptions import AttachedForksError
32 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.vcs import RepositoryError
33 from rhodecode.lib.vcs import RepositoryError
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class RepoSettingsView(RepoAppView):
41 class RepoSettingsView(RepoAppView):
42
42
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45
45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
47 c.repo_info = self.db_repo
48
48
49 self._register_global_c(c)
49 self._register_global_c(c)
50 return c
50 return c
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.admin')
53 @HasRepoPermissionAnyDecorator('repository.admin')
54 @view_config(
54 @view_config(
55 route_name='edit_repo_advanced', request_method='GET',
55 route_name='edit_repo_advanced', request_method='GET',
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 def edit_advanced(self):
57 def edit_advanced(self):
58 c = self.load_default_context()
58 c = self.load_default_context()
59 c.active = 'advanced'
59 c.active = 'advanced'
60
60
61 c.default_user_id = User.get_default_user().user_id
61 c.default_user_id = User.get_default_user().user_id
62 c.in_public_journal = UserFollowing.query() \
62 c.in_public_journal = UserFollowing.query() \
63 .filter(UserFollowing.user_id == c.default_user_id) \
63 .filter(UserFollowing.user_id == c.default_user_id) \
64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
65
65
66 c.has_origin_repo_read_perm = False
66 c.has_origin_repo_read_perm = False
67 if self.db_repo.fork:
67 if self.db_repo.fork:
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 'repository.write', 'repository.read', 'repository.admin')(
69 'repository.write', 'repository.read', 'repository.admin')(
70 self.db_repo.fork.repo_name, 'repo set as fork page')
70 self.db_repo.fork.repo_name, 'repo set as fork page')
71
71
72 return self._get_template_context(c)
72 return self._get_template_context(c)
73
73
74 @LoginRequired()
74 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
75 @HasRepoPermissionAnyDecorator('repository.admin')
76 @view_config(
76 @view_config(
77 route_name='edit_repo_advanced_delete', request_method='POST',
77 route_name='edit_repo_advanced_delete', request_method='POST',
78 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
78 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
79 def edit_advanced_delete(self):
79 def edit_advanced_delete(self):
80 """
80 """
81 Deletes the repository, or shows warnings if deletion is not possible
81 Deletes the repository, or shows warnings if deletion is not possible
82 because of attached forks or other errors.
82 because of attached forks or other errors.
83 """
83 """
84 _ = self.request.translate
84 _ = self.request.translate
85 handle_forks = self.request.POST.get('forks', None)
85 handle_forks = self.request.POST.get('forks', None)
86
86
87 try:
87 try:
88 _forks = self.db_repo.forks.count()
88 _forks = self.db_repo.forks.count()
89 if _forks and handle_forks:
89 if _forks and handle_forks:
90 if handle_forks == 'detach_forks':
90 if handle_forks == 'detach_forks':
91 handle_forks = 'detach'
91 handle_forks = 'detach'
92 h.flash(_('Detached %s forks') % _forks, category='success')
92 h.flash(_('Detached %s forks') % _forks, category='success')
93 elif handle_forks == 'delete_forks':
93 elif handle_forks == 'delete_forks':
94 handle_forks = 'delete'
94 handle_forks = 'delete'
95 h.flash(_('Deleted %s forks') % _forks, category='success')
95 h.flash(_('Deleted %s forks') % _forks, category='success')
96
96
97 repo_data = self.db_repo.get_api_data()
97 repo_data = self.db_repo.get_api_data()
98 RepoModel().delete(self.db_repo, forks=handle_forks)
98 RepoModel().delete(self.db_repo, forks=handle_forks)
99
99
100 repo = audit_logger.RepoWrap(repo_id=None,
100 repo = audit_logger.RepoWrap(repo_id=None,
101 repo_name=self.db_repo.repo_name)
101 repo_name=self.db_repo.repo_name)
102 audit_logger.store(
102 audit_logger.store_web(
103 action='repo.delete',
103 action='repo.delete',
104 action_data={'repo_data': repo_data,
104 action_data={'data': repo_data},
105 'source': audit_logger.SOURCE_WEB},
105 user=self._rhodecode_user, repo=repo)
106 user=self._rhodecode_user, repo=repo, commit=False)
107
106
108 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
107 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
109 h.flash(
108 h.flash(
110 _('Deleted repository `%s`') % self.db_repo_name,
109 _('Deleted repository `%s`') % self.db_repo_name,
111 category='success')
110 category='success')
112 Session().commit()
111 Session().commit()
113 except AttachedForksError:
112 except AttachedForksError:
114 repo_advanced_url = h.route_path(
113 repo_advanced_url = h.route_path(
115 'edit_repo_advanced', repo_name=self.db_repo_name,
114 'edit_repo_advanced', repo_name=self.db_repo_name,
116 _anchor='advanced-delete')
115 _anchor='advanced-delete')
117 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
116 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
118 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
117 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
119 'Try using {delete_or_detach} option.')
118 'Try using {delete_or_detach} option.')
120 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
119 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
121 category='warning')
120 category='warning')
122
121
123 # redirect to advanced for forks handle action ?
122 # redirect to advanced for forks handle action ?
124 raise HTTPFound(repo_advanced_url)
123 raise HTTPFound(repo_advanced_url)
125
124
126 except Exception:
125 except Exception:
127 log.exception("Exception during deletion of repository")
126 log.exception("Exception during deletion of repository")
128 h.flash(_('An error occurred during deletion of `%s`')
127 h.flash(_('An error occurred during deletion of `%s`')
129 % self.db_repo_name, category='error')
128 % self.db_repo_name, category='error')
130 # redirect to advanced for more deletion options
129 # redirect to advanced for more deletion options
131 raise HTTPFound(
130 raise HTTPFound(
132 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
131 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
133 _anchor='advanced-delete')
132 _anchor='advanced-delete')
134
133
135 raise HTTPFound(h.route_path('home'))
134 raise HTTPFound(h.route_path('home'))
136
135
137 @LoginRequired()
136 @LoginRequired()
138 @HasRepoPermissionAnyDecorator('repository.admin')
137 @HasRepoPermissionAnyDecorator('repository.admin')
139 @CSRFRequired()
138 @CSRFRequired()
140 @view_config(
139 @view_config(
141 route_name='edit_repo_advanced_journal', request_method='POST',
140 route_name='edit_repo_advanced_journal', request_method='POST',
142 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
141 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
143 def edit_advanced_journal(self):
142 def edit_advanced_journal(self):
144 """
143 """
145 Set's this repository to be visible in public journal,
144 Set's this repository to be visible in public journal,
146 in other words making default user to follow this repo
145 in other words making default user to follow this repo
147 """
146 """
148 _ = self.request.translate
147 _ = self.request.translate
149
148
150 try:
149 try:
151 user_id = User.get_default_user().user_id
150 user_id = User.get_default_user().user_id
152 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
151 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
153 h.flash(_('Updated repository visibility in public journal'),
152 h.flash(_('Updated repository visibility in public journal'),
154 category='success')
153 category='success')
155 Session().commit()
154 Session().commit()
156 except Exception:
155 except Exception:
157 h.flash(_('An error occurred during setting this '
156 h.flash(_('An error occurred during setting this '
158 'repository in public journal'),
157 'repository in public journal'),
159 category='error')
158 category='error')
160
159
161 raise HTTPFound(
160 raise HTTPFound(
162 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
161 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
163
162
164 @LoginRequired()
163 @LoginRequired()
165 @HasRepoPermissionAnyDecorator('repository.admin')
164 @HasRepoPermissionAnyDecorator('repository.admin')
166 @CSRFRequired()
165 @CSRFRequired()
167 @view_config(
166 @view_config(
168 route_name='edit_repo_advanced_fork', request_method='POST',
167 route_name='edit_repo_advanced_fork', request_method='POST',
169 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
168 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
170 def edit_advanced_fork(self):
169 def edit_advanced_fork(self):
171 """
170 """
172 Mark given repository as a fork of another
171 Mark given repository as a fork of another
173 """
172 """
174 _ = self.request.translate
173 _ = self.request.translate
175
174
176 new_fork_id = self.request.POST.get('id_fork_of')
175 new_fork_id = self.request.POST.get('id_fork_of')
177 try:
176 try:
178
177
179 if new_fork_id and not new_fork_id.isdigit():
178 if new_fork_id and not new_fork_id.isdigit():
180 log.error('Given fork id %s is not an INT', new_fork_id)
179 log.error('Given fork id %s is not an INT', new_fork_id)
181
180
182 fork_id = safe_int(new_fork_id)
181 fork_id = safe_int(new_fork_id)
183 repo = ScmModel().mark_as_fork(
182 repo = ScmModel().mark_as_fork(
184 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
183 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
185 fork = repo.fork.repo_name if repo.fork else _('Nothing')
184 fork = repo.fork.repo_name if repo.fork else _('Nothing')
186 Session().commit()
185 Session().commit()
187 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
186 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
188 category='success')
187 category='success')
189 except RepositoryError as e:
188 except RepositoryError as e:
190 log.exception("Repository Error occurred")
189 log.exception("Repository Error occurred")
191 h.flash(str(e), category='error')
190 h.flash(str(e), category='error')
192 except Exception as e:
191 except Exception as e:
193 log.exception("Exception while editing fork")
192 log.exception("Exception while editing fork")
194 h.flash(_('An error occurred during this operation'),
193 h.flash(_('An error occurred during this operation'),
195 category='error')
194 category='error')
196
195
197 raise HTTPFound(
196 raise HTTPFound(
198 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
197 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
199
198
200 @LoginRequired()
199 @LoginRequired()
201 @HasRepoPermissionAnyDecorator('repository.admin')
200 @HasRepoPermissionAnyDecorator('repository.admin')
202 @CSRFRequired()
201 @CSRFRequired()
203 @view_config(
202 @view_config(
204 route_name='edit_repo_advanced_locking', request_method='POST',
203 route_name='edit_repo_advanced_locking', request_method='POST',
205 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
204 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
206 def edit_advanced_locking(self):
205 def edit_advanced_locking(self):
207 """
206 """
208 Toggle locking of repository
207 Toggle locking of repository
209 """
208 """
210 _ = self.request.translate
209 _ = self.request.translate
211 set_lock = self.request.POST.get('set_lock')
210 set_lock = self.request.POST.get('set_lock')
212 set_unlock = self.request.POST.get('set_unlock')
211 set_unlock = self.request.POST.get('set_unlock')
213
212
214 try:
213 try:
215 if set_lock:
214 if set_lock:
216 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
215 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
217 lock_reason=Repository.LOCK_WEB)
216 lock_reason=Repository.LOCK_WEB)
218 h.flash(_('Locked repository'), category='success')
217 h.flash(_('Locked repository'), category='success')
219 elif set_unlock:
218 elif set_unlock:
220 Repository.unlock(self.db_repo)
219 Repository.unlock(self.db_repo)
221 h.flash(_('Unlocked repository'), category='success')
220 h.flash(_('Unlocked repository'), category='success')
222 except Exception as e:
221 except Exception as e:
223 log.exception("Exception during unlocking")
222 log.exception("Exception during unlocking")
224 h.flash(_('An error occurred during unlocking'), category='error')
223 h.flash(_('An error occurred during unlocking'), category='error')
225
224
226 raise HTTPFound(
225 raise HTTPFound(
227 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
226 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,114 +1,114 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 from pyramid.view import view_config
22 from pyramid.view import view_config
23
23
24 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib import audit_logger
25 from rhodecode.lib import audit_logger
26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
27 NotAnonymous)
27 NotAnonymous)
28 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.ext_json import json
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class StripView(RepoAppView):
33 class StripView(RepoAppView):
34 def load_default_context(self):
34 def load_default_context(self):
35 c = self._get_local_tmpl_context()
35 c = self._get_local_tmpl_context()
36
36
37 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
37 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
38 c.repo_info = self.db_repo
38 c.repo_info = self.db_repo
39
39
40 self._register_global_c(c)
40 self._register_global_c(c)
41 return c
41 return c
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @HasRepoPermissionAnyDecorator('repository.admin')
44 @HasRepoPermissionAnyDecorator('repository.admin')
45 @view_config(
45 @view_config(
46 route_name='strip', request_method='GET',
46 route_name='strip', request_method='GET',
47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
48 def strip(self):
48 def strip(self):
49 c = self.load_default_context()
49 c = self.load_default_context()
50 c.active = 'strip'
50 c.active = 'strip'
51 c.strip_limit = 10
51 c.strip_limit = 10
52
52
53 return self._get_template_context(c)
53 return self._get_template_context(c)
54
54
55 @LoginRequired()
55 @LoginRequired()
56 @HasRepoPermissionAnyDecorator('repository.admin')
56 @HasRepoPermissionAnyDecorator('repository.admin')
57 @view_config(
57 @view_config(
58 route_name='strip_check', request_method='POST',
58 route_name='strip_check', request_method='POST',
59 renderer='json', xhr=True)
59 renderer='json', xhr=True)
60 def strip_check(self):
60 def strip_check(self):
61 from rhodecode.lib.vcs.backends.base import EmptyCommit
61 from rhodecode.lib.vcs.backends.base import EmptyCommit
62 data = {}
62 data = {}
63 rp = self.request.POST
63 rp = self.request.POST
64 for i in range(1, 11):
64 for i in range(1, 11):
65 chset = 'changeset_id-%d' % (i,)
65 chset = 'changeset_id-%d' % (i,)
66 check = rp.get(chset)
66 check = rp.get(chset)
67 if check:
67 if check:
68 data[i] = self.db_repo.get_changeset(rp[chset])
68 data[i] = self.db_repo.get_changeset(rp[chset])
69 if isinstance(data[i], EmptyCommit):
69 if isinstance(data[i], EmptyCommit):
70 data[i] = {'rev': None, 'commit': rp[chset]}
70 data[i] = {'rev': None, 'commit': rp[chset]}
71 else:
71 else:
72 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
72 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
73 'author': data[i].author,
73 'author': data[i].author,
74 'comment': data[i].message}
74 'comment': data[i].message}
75 else:
75 else:
76 break
76 break
77 return data
77 return data
78
78
79 @LoginRequired()
79 @LoginRequired()
80 @HasRepoPermissionAnyDecorator('repository.admin')
80 @HasRepoPermissionAnyDecorator('repository.admin')
81 @view_config(
81 @view_config(
82 route_name='strip_execute', request_method='POST',
82 route_name='strip_execute', request_method='POST',
83 renderer='json', xhr=True)
83 renderer='json', xhr=True)
84 def strip_execute(self):
84 def strip_execute(self):
85 from rhodecode.model.scm import ScmModel
85 from rhodecode.model.scm import ScmModel
86
86
87 c = self.load_default_context()
87 c = self.load_default_context()
88 user = self._rhodecode_user
88 user = self._rhodecode_user
89 rp = self.request.POST
89 rp = self.request.POST
90 data = {}
90 data = {}
91 for idx in rp:
91 for idx in rp:
92 commit = json.loads(rp[idx])
92 commit = json.loads(rp[idx])
93 # If someone put two times the same branch
93 # If someone put two times the same branch
94 if commit['branch'] in data.keys():
94 if commit['branch'] in data.keys():
95 continue
95 continue
96 try:
96 try:
97 ScmModel().strip(
97 ScmModel().strip(
98 repo=c.repo_info,
98 repo=c.repo_info,
99 commit_id=commit['rev'], branch=commit['branch'])
99 commit_id=commit['rev'], branch=commit['branch'])
100 log.info('Stripped commit %s from repo `%s` by %s' % (
100 log.info('Stripped commit %s from repo `%s` by %s' % (
101 commit['rev'], c.repo_info.repo_name, user))
101 commit['rev'], c.repo_info.repo_name, user))
102 data[commit['rev']] = True
102 data[commit['rev']] = True
103
103
104 audit_logger.store(
104 audit_logger.store_web(
105 action='repo.commit.strip',
105 action='repo.commit.strip',
106 action_data={'commit_id': commit['rev']},
106 action_data={'commit_id': commit['rev']},
107 repo=self.db_repo,
107 repo=self.db_repo,
108 user=self._rhodecode_user, commit=True)
108 user=self._rhodecode_user, commit=True)
109
109
110 except Exception as e:
110 except Exception as e:
111 data[commit['rev']] = False
111 data[commit['rev']] = False
112 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
112 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
113 commit['rev'], self.db_repo_name, user, e.message))
113 commit['rev'], self.db_repo_name, user, e.message))
114 return data
114 return data
@@ -1,406 +1,404 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Repository groups controller for RhodeCode
23 Repository groups controller for RhodeCode
24 """
24 """
25
25
26 import logging
26 import logging
27 import formencode
27 import formencode
28
28
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from pylons import request, tmpl_context as c, url
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _, ungettext
33 from pylons.i18n.translation import _, ungettext
34
34
35 from rhodecode.lib import auth
35 from rhodecode.lib import auth
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, NotAnonymous, HasPermissionAll,
40 LoginRequired, NotAnonymous, HasPermissionAll,
41 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils2 import safe_int
43 from rhodecode.lib.utils2 import safe_int
44 from rhodecode.model.db import RepoGroup, User
44 from rhodecode.model.db import RepoGroup, User
45 from rhodecode.model.scm import RepoGroupList
45 from rhodecode.model.scm import RepoGroupList
46 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.repo_group import RepoGroupModel
47 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
47 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class RepoGroupsController(BaseController):
54 class RepoGroupsController(BaseController):
55 """REST Controller styled on the Atom Publishing Protocol"""
55 """REST Controller styled on the Atom Publishing Protocol"""
56
56
57 @LoginRequired()
57 @LoginRequired()
58 def __before__(self):
58 def __before__(self):
59 super(RepoGroupsController, self).__before__()
59 super(RepoGroupsController, self).__before__()
60
60
61 def __load_defaults(self, allow_empty_group=False, repo_group=None):
61 def __load_defaults(self, allow_empty_group=False, repo_group=None):
62 if self._can_create_repo_group():
62 if self._can_create_repo_group():
63 # we're global admin, we're ok and we can create TOP level groups
63 # we're global admin, we're ok and we can create TOP level groups
64 allow_empty_group = True
64 allow_empty_group = True
65
65
66 # override the choices for this form, we need to filter choices
66 # override the choices for this form, we need to filter choices
67 # and display only those we have ADMIN right
67 # and display only those we have ADMIN right
68 groups_with_admin_rights = RepoGroupList(
68 groups_with_admin_rights = RepoGroupList(
69 RepoGroup.query().all(),
69 RepoGroup.query().all(),
70 perm_set=['group.admin'])
70 perm_set=['group.admin'])
71 c.repo_groups = RepoGroup.groups_choices(
71 c.repo_groups = RepoGroup.groups_choices(
72 groups=groups_with_admin_rights,
72 groups=groups_with_admin_rights,
73 show_empty_group=allow_empty_group)
73 show_empty_group=allow_empty_group)
74
74
75 if repo_group:
75 if repo_group:
76 # exclude filtered ids
76 # exclude filtered ids
77 exclude_group_ids = [repo_group.group_id]
77 exclude_group_ids = [repo_group.group_id]
78 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
78 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
79 c.repo_groups)
79 c.repo_groups)
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
81 parent_group = repo_group.parent_group
81 parent_group = repo_group.parent_group
82
82
83 add_parent_group = (parent_group and (
83 add_parent_group = (parent_group and (
84 unicode(parent_group.group_id) not in c.repo_groups_choices))
84 unicode(parent_group.group_id) not in c.repo_groups_choices))
85 if add_parent_group:
85 if add_parent_group:
86 c.repo_groups_choices.append(unicode(parent_group.group_id))
86 c.repo_groups_choices.append(unicode(parent_group.group_id))
87 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
87 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
88
88
89 def __load_data(self, group_id):
89 def __load_data(self, group_id):
90 """
90 """
91 Load defaults settings for edit, and update
91 Load defaults settings for edit, and update
92
92
93 :param group_id:
93 :param group_id:
94 """
94 """
95 repo_group = RepoGroup.get_or_404(group_id)
95 repo_group = RepoGroup.get_or_404(group_id)
96 data = repo_group.get_dict()
96 data = repo_group.get_dict()
97 data['group_name'] = repo_group.name
97 data['group_name'] = repo_group.name
98
98
99 # fill owner
99 # fill owner
100 if repo_group.user:
100 if repo_group.user:
101 data.update({'user': repo_group.user.username})
101 data.update({'user': repo_group.user.username})
102 else:
102 else:
103 replacement_user = User.get_first_super_admin().username
103 replacement_user = User.get_first_super_admin().username
104 data.update({'user': replacement_user})
104 data.update({'user': replacement_user})
105
105
106 # fill repository group users
106 # fill repository group users
107 for p in repo_group.repo_group_to_perm:
107 for p in repo_group.repo_group_to_perm:
108 data.update({
108 data.update({
109 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
109 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
110
110
111 # fill repository group user groups
111 # fill repository group user groups
112 for p in repo_group.users_group_to_perm:
112 for p in repo_group.users_group_to_perm:
113 data.update({
113 data.update({
114 'g_perm_%s' % p.users_group.users_group_id:
114 'g_perm_%s' % p.users_group.users_group_id:
115 p.permission.permission_name})
115 p.permission.permission_name})
116 # html and form expects -1 as empty parent group
116 # html and form expects -1 as empty parent group
117 data['group_parent_id'] = data['group_parent_id'] or -1
117 data['group_parent_id'] = data['group_parent_id'] or -1
118 return data
118 return data
119
119
120 def _revoke_perms_on_yourself(self, form_result):
120 def _revoke_perms_on_yourself(self, form_result):
121 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
121 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
122 form_result['perm_updates'])
122 form_result['perm_updates'])
123 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
123 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
124 form_result['perm_additions'])
124 form_result['perm_additions'])
125 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
125 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
126 form_result['perm_deletions'])
126 form_result['perm_deletions'])
127 admin_perm = 'group.admin'
127 admin_perm = 'group.admin'
128 if _updates and _updates[0][1] != admin_perm or \
128 if _updates and _updates[0][1] != admin_perm or \
129 _additions and _additions[0][1] != admin_perm or \
129 _additions and _additions[0][1] != admin_perm or \
130 _deletions and _deletions[0][1] != admin_perm:
130 _deletions and _deletions[0][1] != admin_perm:
131 return True
131 return True
132 return False
132 return False
133
133
134 def _can_create_repo_group(self, parent_group_id=None):
134 def _can_create_repo_group(self, parent_group_id=None):
135 is_admin = HasPermissionAll('hg.admin')('group create controller')
135 is_admin = HasPermissionAll('hg.admin')('group create controller')
136 create_repo_group = HasPermissionAll(
136 create_repo_group = HasPermissionAll(
137 'hg.repogroup.create.true')('group create controller')
137 'hg.repogroup.create.true')('group create controller')
138 if is_admin or (create_repo_group and not parent_group_id):
138 if is_admin or (create_repo_group and not parent_group_id):
139 # we're global admin, or we have global repo group create
139 # we're global admin, or we have global repo group create
140 # permission
140 # permission
141 # we're ok and we can create TOP level groups
141 # we're ok and we can create TOP level groups
142 return True
142 return True
143 elif parent_group_id:
143 elif parent_group_id:
144 # we check the permission if we can write to parent group
144 # we check the permission if we can write to parent group
145 group = RepoGroup.get(parent_group_id)
145 group = RepoGroup.get(parent_group_id)
146 group_name = group.group_name if group else None
146 group_name = group.group_name if group else None
147 if HasRepoGroupPermissionAll('group.admin')(
147 if HasRepoGroupPermissionAll('group.admin')(
148 group_name, 'check if user is an admin of group'):
148 group_name, 'check if user is an admin of group'):
149 # we're an admin of passed in group, we're ok.
149 # we're an admin of passed in group, we're ok.
150 return True
150 return True
151 else:
151 else:
152 return False
152 return False
153 return False
153 return False
154
154
155 @NotAnonymous()
155 @NotAnonymous()
156 def index(self):
156 def index(self):
157 repo_group_list = RepoGroup.get_all_repo_groups()
157 repo_group_list = RepoGroup.get_all_repo_groups()
158 _perms = ['group.admin']
158 _perms = ['group.admin']
159 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
159 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
160 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
160 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
161 repo_group_list=repo_group_list_acl, admin=True)
161 repo_group_list=repo_group_list_acl, admin=True)
162 c.data = json.dumps(repo_group_data)
162 c.data = json.dumps(repo_group_data)
163 return render('admin/repo_groups/repo_groups.mako')
163 return render('admin/repo_groups/repo_groups.mako')
164
164
165 # perm checks inside
165 # perm checks inside
166 @NotAnonymous()
166 @NotAnonymous()
167 @auth.CSRFRequired()
167 @auth.CSRFRequired()
168 def create(self):
168 def create(self):
169
169
170 parent_group_id = safe_int(request.POST.get('group_parent_id'))
170 parent_group_id = safe_int(request.POST.get('group_parent_id'))
171 can_create = self._can_create_repo_group(parent_group_id)
171 can_create = self._can_create_repo_group(parent_group_id)
172
172
173 self.__load_defaults()
173 self.__load_defaults()
174 # permissions for can create group based on parent_id are checked
174 # permissions for can create group based on parent_id are checked
175 # here in the Form
175 # here in the Form
176 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
176 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
177 repo_group_form = RepoGroupForm(available_groups=available_groups,
177 repo_group_form = RepoGroupForm(available_groups=available_groups,
178 can_create_in_root=can_create)()
178 can_create_in_root=can_create)()
179 try:
179 try:
180 owner = c.rhodecode_user
180 owner = c.rhodecode_user
181 form_result = repo_group_form.to_python(dict(request.POST))
181 form_result = repo_group_form.to_python(dict(request.POST))
182 repo_group = RepoGroupModel().create(
182 repo_group = RepoGroupModel().create(
183 group_name=form_result['group_name_full'],
183 group_name=form_result['group_name_full'],
184 group_description=form_result['group_description'],
184 group_description=form_result['group_description'],
185 owner=owner.user_id,
185 owner=owner.user_id,
186 copy_permissions=form_result['group_copy_permissions']
186 copy_permissions=form_result['group_copy_permissions']
187 )
187 )
188 Session().commit()
188 Session().commit()
189 repo_group_data = repo_group.get_api_data()
189 repo_group_data = repo_group.get_api_data()
190 _new_group_name = form_result['group_name_full']
190 _new_group_name = form_result['group_name_full']
191
191
192 audit_logger.store(
192 audit_logger.store_web(
193 action='repo_group.create',
193 action='repo_group.create',
194 action_data={'repo_group_data': repo_group_data},
194 action_data={'data': repo_group_data},
195 user=c.rhodecode_user, commit=True)
195 user=c.rhodecode_user, commit=True)
196
196
197 repo_group_url = h.link_to(
197 repo_group_url = h.link_to(
198 _new_group_name,
198 _new_group_name,
199 h.route_path('repo_group_home', repo_group_name=_new_group_name))
199 h.route_path('repo_group_home', repo_group_name=_new_group_name))
200 h.flash(h.literal(_('Created repository group %s')
200 h.flash(h.literal(_('Created repository group %s')
201 % repo_group_url), category='success')
201 % repo_group_url), category='success')
202
202
203 except formencode.Invalid as errors:
203 except formencode.Invalid as errors:
204 return htmlfill.render(
204 return htmlfill.render(
205 render('admin/repo_groups/repo_group_add.mako'),
205 render('admin/repo_groups/repo_group_add.mako'),
206 defaults=errors.value,
206 defaults=errors.value,
207 errors=errors.error_dict or {},
207 errors=errors.error_dict or {},
208 prefix_error=False,
208 prefix_error=False,
209 encoding="UTF-8",
209 encoding="UTF-8",
210 force_defaults=False)
210 force_defaults=False)
211 except Exception:
211 except Exception:
212 log.exception("Exception during creation of repository group")
212 log.exception("Exception during creation of repository group")
213 h.flash(_('Error occurred during creation of repository group %s')
213 h.flash(_('Error occurred during creation of repository group %s')
214 % request.POST.get('group_name'), category='error')
214 % request.POST.get('group_name'), category='error')
215
215
216 # TODO: maybe we should get back to the main view, not the admin one
216 # TODO: maybe we should get back to the main view, not the admin one
217 return redirect(url('repo_groups', parent_group=parent_group_id))
217 return redirect(url('repo_groups', parent_group=parent_group_id))
218
218
219 # perm checks inside
219 # perm checks inside
220 @NotAnonymous()
220 @NotAnonymous()
221 def new(self):
221 def new(self):
222 # perm check for admin, create_group perm or admin of parent_group
222 # perm check for admin, create_group perm or admin of parent_group
223 parent_group_id = safe_int(request.GET.get('parent_group'))
223 parent_group_id = safe_int(request.GET.get('parent_group'))
224 if not self._can_create_repo_group(parent_group_id):
224 if not self._can_create_repo_group(parent_group_id):
225 return abort(403)
225 return abort(403)
226
226
227 self.__load_defaults()
227 self.__load_defaults()
228 return render('admin/repo_groups/repo_group_add.mako')
228 return render('admin/repo_groups/repo_group_add.mako')
229
229
230 @HasRepoGroupPermissionAnyDecorator('group.admin')
230 @HasRepoGroupPermissionAnyDecorator('group.admin')
231 @auth.CSRFRequired()
231 @auth.CSRFRequired()
232 def update(self, group_name):
232 def update(self, group_name):
233
233
234 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
234 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
235 can_create_in_root = self._can_create_repo_group()
235 can_create_in_root = self._can_create_repo_group()
236 show_root_location = can_create_in_root
236 show_root_location = can_create_in_root
237 if not c.repo_group.parent_group:
237 if not c.repo_group.parent_group:
238 # this group don't have a parrent so we should show empty value
238 # this group don't have a parrent so we should show empty value
239 show_root_location = True
239 show_root_location = True
240 self.__load_defaults(allow_empty_group=show_root_location,
240 self.__load_defaults(allow_empty_group=show_root_location,
241 repo_group=c.repo_group)
241 repo_group=c.repo_group)
242
242
243 repo_group_form = RepoGroupForm(
243 repo_group_form = RepoGroupForm(
244 edit=True, old_data=c.repo_group.get_dict(),
244 edit=True, old_data=c.repo_group.get_dict(),
245 available_groups=c.repo_groups_choices,
245 available_groups=c.repo_groups_choices,
246 can_create_in_root=can_create_in_root, allow_disabled=True)()
246 can_create_in_root=can_create_in_root, allow_disabled=True)()
247
247
248 old_values = c.repo_group.get_api_data()
248 old_values = c.repo_group.get_api_data()
249 try:
249 try:
250 form_result = repo_group_form.to_python(dict(request.POST))
250 form_result = repo_group_form.to_python(dict(request.POST))
251 gr_name = form_result['group_name']
251 gr_name = form_result['group_name']
252 new_gr = RepoGroupModel().update(group_name, form_result)
252 new_gr = RepoGroupModel().update(group_name, form_result)
253
253
254 audit_logger.store(
254 audit_logger.store_web(
255 'repo_group.edit', action_data={'old_data': old_values},
255 'repo_group.edit', action_data={'old_data': old_values},
256 user=c.rhodecode_user)
256 user=c.rhodecode_user)
257
257
258 Session().commit()
258 Session().commit()
259 h.flash(_('Updated repository group %s') % (gr_name,),
259 h.flash(_('Updated repository group %s') % (gr_name,),
260 category='success')
260 category='success')
261 # we now have new name !
261 # we now have new name !
262 group_name = new_gr.group_name
262 group_name = new_gr.group_name
263 except formencode.Invalid as errors:
263 except formencode.Invalid as errors:
264 c.active = 'settings'
264 c.active = 'settings'
265 return htmlfill.render(
265 return htmlfill.render(
266 render('admin/repo_groups/repo_group_edit.mako'),
266 render('admin/repo_groups/repo_group_edit.mako'),
267 defaults=errors.value,
267 defaults=errors.value,
268 errors=errors.error_dict or {},
268 errors=errors.error_dict or {},
269 prefix_error=False,
269 prefix_error=False,
270 encoding="UTF-8",
270 encoding="UTF-8",
271 force_defaults=False)
271 force_defaults=False)
272 except Exception:
272 except Exception:
273 log.exception("Exception during update or repository group")
273 log.exception("Exception during update or repository group")
274 h.flash(_('Error occurred during update of repository group %s')
274 h.flash(_('Error occurred during update of repository group %s')
275 % request.POST.get('group_name'), category='error')
275 % request.POST.get('group_name'), category='error')
276
276
277 return redirect(url('edit_repo_group', group_name=group_name))
277 return redirect(url('edit_repo_group', group_name=group_name))
278
278
279 @HasRepoGroupPermissionAnyDecorator('group.admin')
279 @HasRepoGroupPermissionAnyDecorator('group.admin')
280 @auth.CSRFRequired()
280 @auth.CSRFRequired()
281 def delete(self, group_name):
281 def delete(self, group_name):
282 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
282 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
283 repos = gr.repositories.all()
283 repos = gr.repositories.all()
284 if repos:
284 if repos:
285 msg = ungettext(
285 msg = ungettext(
286 'This group contains %(num)d repository and cannot be deleted',
286 'This group contains %(num)d repository and cannot be deleted',
287 'This group contains %(num)d repositories and cannot be'
287 'This group contains %(num)d repositories and cannot be'
288 ' deleted',
288 ' deleted',
289 len(repos)) % {'num': len(repos)}
289 len(repos)) % {'num': len(repos)}
290 h.flash(msg, category='warning')
290 h.flash(msg, category='warning')
291 return redirect(url('repo_groups'))
291 return redirect(url('repo_groups'))
292
292
293 children = gr.children.all()
293 children = gr.children.all()
294 if children:
294 if children:
295 msg = ungettext(
295 msg = ungettext(
296 'This group contains %(num)d subgroup and cannot be deleted',
296 'This group contains %(num)d subgroup and cannot be deleted',
297 'This group contains %(num)d subgroups and cannot be deleted',
297 'This group contains %(num)d subgroups and cannot be deleted',
298 len(children)) % {'num': len(children)}
298 len(children)) % {'num': len(children)}
299 h.flash(msg, category='warning')
299 h.flash(msg, category='warning')
300 return redirect(url('repo_groups'))
300 return redirect(url('repo_groups'))
301
301
302 try:
302 try:
303 old_values = gr.get_api_data()
303 old_values = gr.get_api_data()
304 RepoGroupModel().delete(group_name)
304 RepoGroupModel().delete(group_name)
305
305
306 audit_logger.store(
306 audit_logger.store_web(
307 'repo_group.delete',
307 'repo_group.delete',
308 action_data={'old_data': old_values,
308 action_data={'old_data': old_values},
309 'source': audit_logger.SOURCE_WEB},
310 user=c.rhodecode_user)
309 user=c.rhodecode_user)
311
310
312 Session().commit()
311 Session().commit()
313 h.flash(_('Removed repository group %s') % group_name,
312 h.flash(_('Removed repository group %s') % group_name,
314 category='success')
313 category='success')
315 except Exception:
314 except Exception:
316 log.exception("Exception during deletion of repository group")
315 log.exception("Exception during deletion of repository group")
317 h.flash(_('Error occurred during deletion of repository group %s')
316 h.flash(_('Error occurred during deletion of repository group %s')
318 % group_name, category='error')
317 % group_name, category='error')
319
318
320 return redirect(url('repo_groups'))
319 return redirect(url('repo_groups'))
321
320
322 @HasRepoGroupPermissionAnyDecorator('group.admin')
321 @HasRepoGroupPermissionAnyDecorator('group.admin')
323 def edit(self, group_name):
322 def edit(self, group_name):
324
323
325 c.active = 'settings'
324 c.active = 'settings'
326
325
327 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
326 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
328 # we can only allow moving empty group if it's already a top-level
327 # we can only allow moving empty group if it's already a top-level
329 # group, ie has no parents, or we're admin
328 # group, ie has no parents, or we're admin
330 can_create_in_root = self._can_create_repo_group()
329 can_create_in_root = self._can_create_repo_group()
331 show_root_location = can_create_in_root
330 show_root_location = can_create_in_root
332 if not c.repo_group.parent_group:
331 if not c.repo_group.parent_group:
333 # this group don't have a parrent so we should show empty value
332 # this group don't have a parrent so we should show empty value
334 show_root_location = True
333 show_root_location = True
335 self.__load_defaults(allow_empty_group=show_root_location,
334 self.__load_defaults(allow_empty_group=show_root_location,
336 repo_group=c.repo_group)
335 repo_group=c.repo_group)
337 defaults = self.__load_data(c.repo_group.group_id)
336 defaults = self.__load_data(c.repo_group.group_id)
338
337
339 return htmlfill.render(
338 return htmlfill.render(
340 render('admin/repo_groups/repo_group_edit.mako'),
339 render('admin/repo_groups/repo_group_edit.mako'),
341 defaults=defaults,
340 defaults=defaults,
342 encoding="UTF-8",
341 encoding="UTF-8",
343 force_defaults=False
342 force_defaults=False
344 )
343 )
345
344
346 @HasRepoGroupPermissionAnyDecorator('group.admin')
345 @HasRepoGroupPermissionAnyDecorator('group.admin')
347 def edit_repo_group_advanced(self, group_name):
346 def edit_repo_group_advanced(self, group_name):
348 c.active = 'advanced'
347 c.active = 'advanced'
349 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
348 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
350
349
351 return render('admin/repo_groups/repo_group_edit.mako')
350 return render('admin/repo_groups/repo_group_edit.mako')
352
351
353 @HasRepoGroupPermissionAnyDecorator('group.admin')
352 @HasRepoGroupPermissionAnyDecorator('group.admin')
354 def edit_repo_group_perms(self, group_name):
353 def edit_repo_group_perms(self, group_name):
355 c.active = 'perms'
354 c.active = 'perms'
356 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
355 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
357 self.__load_defaults()
356 self.__load_defaults()
358 defaults = self.__load_data(c.repo_group.group_id)
357 defaults = self.__load_data(c.repo_group.group_id)
359
358
360 return htmlfill.render(
359 return htmlfill.render(
361 render('admin/repo_groups/repo_group_edit.mako'),
360 render('admin/repo_groups/repo_group_edit.mako'),
362 defaults=defaults,
361 defaults=defaults,
363 encoding="UTF-8",
362 encoding="UTF-8",
364 force_defaults=False
363 force_defaults=False
365 )
364 )
366
365
367 @HasRepoGroupPermissionAnyDecorator('group.admin')
366 @HasRepoGroupPermissionAnyDecorator('group.admin')
368 @auth.CSRFRequired()
367 @auth.CSRFRequired()
369 def update_perms(self, group_name):
368 def update_perms(self, group_name):
370 """
369 """
371 Update permissions for given repository group
370 Update permissions for given repository group
372 """
371 """
373
372
374 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
373 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
375 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
374 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
376 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
375 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
377 request.POST)
376 request.POST)
378
377
379 if not c.rhodecode_user.is_admin:
378 if not c.rhodecode_user.is_admin:
380 if self._revoke_perms_on_yourself(form):
379 if self._revoke_perms_on_yourself(form):
381 msg = _('Cannot change permission for yourself as admin')
380 msg = _('Cannot change permission for yourself as admin')
382 h.flash(msg, category='warning')
381 h.flash(msg, category='warning')
383 return redirect(
382 return redirect(
384 url('edit_repo_group_perms', group_name=group_name))
383 url('edit_repo_group_perms', group_name=group_name))
385
384
386 # iterate over all members(if in recursive mode) of this groups and
385 # iterate over all members(if in recursive mode) of this groups and
387 # set the permissions !
386 # set the permissions !
388 # this can be potentially heavy operation
387 # this can be potentially heavy operation
389 changes = RepoGroupModel().update_permissions(
388 changes = RepoGroupModel().update_permissions(
390 c.repo_group,
389 c.repo_group,
391 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
390 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
392 form['recursive'])
391 form['recursive'])
393
392
394 action_data = {
393 action_data = {
395 'added': changes['added'],
394 'added': changes['added'],
396 'updated': changes['updated'],
395 'updated': changes['updated'],
397 'deleted': changes['deleted'],
396 'deleted': changes['deleted'],
398 'source': audit_logger.SOURCE_WEB
399 }
397 }
400 audit_logger.store(
398 audit_logger.store_web(
401 'repo_group.edit.permissions', action_data=action_data,
399 'repo_group.edit.permissions', action_data=action_data,
402 user=c.rhodecode_user)
400 user=c.rhodecode_user)
403
401
404 Session().commit()
402 Session().commit()
405 h.flash(_('Repository Group permissions updated'), category='success')
403 h.flash(_('Repository Group permissions updated'), category='success')
406 return redirect(url('edit_repo_group_perms', group_name=group_name))
404 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -1,1110 +1,1110 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Files controller for RhodeCode Enterprise
22 Files controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import itertools
25 import itertools
26 import logging
26 import logging
27 import os
27 import os
28 import shutil
28 import shutil
29 import tempfile
29 import tempfile
30
30
31 from pylons import request, response, tmpl_context as c, url
31 from pylons import request, response, tmpl_context as c, url
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from webob.exc import HTTPNotFound, HTTPBadRequest
34 from webob.exc import HTTPNotFound, HTTPBadRequest
35
35
36 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.controllers.utils import parse_path_ref
37 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import diffs, helpers as h, caches
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils import jsonify, action_logger
41 from rhodecode.lib.utils import jsonify, action_logger
42 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
43 convert_line_endings, detect_mode, safe_str, str2bool)
43 convert_line_endings, detect_mode, safe_str, str2bool)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
46 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.base import BaseRepoController, render
47 from rhodecode.lib.vcs import path as vcspath
47 from rhodecode.lib.vcs import path as vcspath
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 from rhodecode.lib.vcs.conf import settings
49 from rhodecode.lib.vcs.conf import settings
50 from rhodecode.lib.vcs.exceptions import (
50 from rhodecode.lib.vcs.exceptions import (
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 NodeDoesNotExistError, CommitError, NodeError)
53 NodeDoesNotExistError, CommitError, NodeError)
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55
55
56 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.db import Repository
58 from rhodecode.model.db import Repository
59
59
60 from rhodecode.controllers.changeset import (
60 from rhodecode.controllers.changeset import (
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
62 from rhodecode.lib.exceptions import NonRelativePathError
62 from rhodecode.lib.exceptions import NonRelativePathError
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 class FilesController(BaseRepoController):
67 class FilesController(BaseRepoController):
68
68
69 def __before__(self):
69 def __before__(self):
70 super(FilesController, self).__before__()
70 super(FilesController, self).__before__()
71 c.cut_off_limit = self.cut_off_limit_file
71 c.cut_off_limit = self.cut_off_limit_file
72
72
73 def _get_default_encoding(self):
73 def _get_default_encoding(self):
74 enc_list = getattr(c, 'default_encodings', [])
74 enc_list = getattr(c, 'default_encodings', [])
75 return enc_list[0] if enc_list else 'UTF-8'
75 return enc_list[0] if enc_list else 'UTF-8'
76
76
77 def __get_commit_or_redirect(self, commit_id, repo_name,
77 def __get_commit_or_redirect(self, commit_id, repo_name,
78 redirect_after=True):
78 redirect_after=True):
79 """
79 """
80 This is a safe way to get commit. If an error occurs it redirects to
80 This is a safe way to get commit. If an error occurs it redirects to
81 tip with proper message
81 tip with proper message
82
82
83 :param commit_id: id of commit to fetch
83 :param commit_id: id of commit to fetch
84 :param repo_name: repo name to redirect after
84 :param repo_name: repo name to redirect after
85 :param redirect_after: toggle redirection
85 :param redirect_after: toggle redirection
86 """
86 """
87 try:
87 try:
88 return c.rhodecode_repo.get_commit(commit_id)
88 return c.rhodecode_repo.get_commit(commit_id)
89 except EmptyRepositoryError:
89 except EmptyRepositoryError:
90 if not redirect_after:
90 if not redirect_after:
91 return None
91 return None
92 url_ = url('files_add_home',
92 url_ = url('files_add_home',
93 repo_name=c.repo_name,
93 repo_name=c.repo_name,
94 revision=0, f_path='', anchor='edit')
94 revision=0, f_path='', anchor='edit')
95 if h.HasRepoPermissionAny(
95 if h.HasRepoPermissionAny(
96 'repository.write', 'repository.admin')(c.repo_name):
96 'repository.write', 'repository.admin')(c.repo_name):
97 add_new = h.link_to(
97 add_new = h.link_to(
98 _('Click here to add a new file.'),
98 _('Click here to add a new file.'),
99 url_, class_="alert-link")
99 url_, class_="alert-link")
100 else:
100 else:
101 add_new = ""
101 add_new = ""
102 h.flash(h.literal(
102 h.flash(h.literal(
103 _('There are no files yet. %s') % add_new), category='warning')
103 _('There are no files yet. %s') % add_new), category='warning')
104 redirect(h.route_path('repo_summary', repo_name=repo_name))
104 redirect(h.route_path('repo_summary', repo_name=repo_name))
105 except (CommitDoesNotExistError, LookupError):
105 except (CommitDoesNotExistError, LookupError):
106 msg = _('No such commit exists for this repository')
106 msg = _('No such commit exists for this repository')
107 h.flash(msg, category='error')
107 h.flash(msg, category='error')
108 raise HTTPNotFound()
108 raise HTTPNotFound()
109 except RepositoryError as e:
109 except RepositoryError as e:
110 h.flash(safe_str(e), category='error')
110 h.flash(safe_str(e), category='error')
111 raise HTTPNotFound()
111 raise HTTPNotFound()
112
112
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
114 """
114 """
115 Returns file_node, if error occurs or given path is directory,
115 Returns file_node, if error occurs or given path is directory,
116 it'll redirect to top level path
116 it'll redirect to top level path
117
117
118 :param repo_name: repo_name
118 :param repo_name: repo_name
119 :param commit: given commit
119 :param commit: given commit
120 :param path: path to lookup
120 :param path: path to lookup
121 """
121 """
122 try:
122 try:
123 file_node = commit.get_node(path)
123 file_node = commit.get_node(path)
124 if file_node.is_dir():
124 if file_node.is_dir():
125 raise RepositoryError('The given path is a directory')
125 raise RepositoryError('The given path is a directory')
126 except CommitDoesNotExistError:
126 except CommitDoesNotExistError:
127 msg = _('No such commit exists for this repository')
127 msg = _('No such commit exists for this repository')
128 log.exception(msg)
128 log.exception(msg)
129 h.flash(msg, category='error')
129 h.flash(msg, category='error')
130 raise HTTPNotFound()
130 raise HTTPNotFound()
131 except RepositoryError as e:
131 except RepositoryError as e:
132 h.flash(safe_str(e), category='error')
132 h.flash(safe_str(e), category='error')
133 raise HTTPNotFound()
133 raise HTTPNotFound()
134
134
135 return file_node
135 return file_node
136
136
137 def __get_tree_cache_manager(self, repo_name, namespace_type):
137 def __get_tree_cache_manager(self, repo_name, namespace_type):
138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
138 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
139 return caches.get_cache_manager('repo_cache_long', _namespace)
139 return caches.get_cache_manager('repo_cache_long', _namespace)
140
140
141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
141 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
142 full_load=False, force=False):
142 full_load=False, force=False):
143 def _cached_tree():
143 def _cached_tree():
144 log.debug('Generating cached file tree for %s, %s, %s',
144 log.debug('Generating cached file tree for %s, %s, %s',
145 repo_name, commit_id, f_path)
145 repo_name, commit_id, f_path)
146 c.full_load = full_load
146 c.full_load = full_load
147 return render('files/files_browser_tree.mako')
147 return render('files/files_browser_tree.mako')
148
148
149 cache_manager = self.__get_tree_cache_manager(
149 cache_manager = self.__get_tree_cache_manager(
150 repo_name, caches.FILE_TREE)
150 repo_name, caches.FILE_TREE)
151
151
152 cache_key = caches.compute_key_from_params(
152 cache_key = caches.compute_key_from_params(
153 repo_name, commit_id, f_path)
153 repo_name, commit_id, f_path)
154
154
155 if force:
155 if force:
156 # we want to force recompute of caches
156 # we want to force recompute of caches
157 cache_manager.remove_value(cache_key)
157 cache_manager.remove_value(cache_key)
158
158
159 return cache_manager.get(cache_key, createfunc=_cached_tree)
159 return cache_manager.get(cache_key, createfunc=_cached_tree)
160
160
161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
161 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
162 def _cached_nodes():
162 def _cached_nodes():
163 log.debug('Generating cached nodelist for %s, %s, %s',
163 log.debug('Generating cached nodelist for %s, %s, %s',
164 repo_name, commit_id, f_path)
164 repo_name, commit_id, f_path)
165 _d, _f = ScmModel().get_nodes(
165 _d, _f = ScmModel().get_nodes(
166 repo_name, commit_id, f_path, flat=False)
166 repo_name, commit_id, f_path, flat=False)
167 return _d + _f
167 return _d + _f
168
168
169 cache_manager = self.__get_tree_cache_manager(
169 cache_manager = self.__get_tree_cache_manager(
170 repo_name, caches.FILE_SEARCH_TREE_META)
170 repo_name, caches.FILE_SEARCH_TREE_META)
171
171
172 cache_key = caches.compute_key_from_params(
172 cache_key = caches.compute_key_from_params(
173 repo_name, commit_id, f_path)
173 repo_name, commit_id, f_path)
174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
174 return cache_manager.get(cache_key, createfunc=_cached_nodes)
175
175
176 @LoginRequired()
176 @LoginRequired()
177 @HasRepoPermissionAnyDecorator(
177 @HasRepoPermissionAnyDecorator(
178 'repository.read', 'repository.write', 'repository.admin')
178 'repository.read', 'repository.write', 'repository.admin')
179 def index(
179 def index(
180 self, repo_name, revision, f_path, annotate=False, rendered=False):
180 self, repo_name, revision, f_path, annotate=False, rendered=False):
181 commit_id = revision
181 commit_id = revision
182
182
183 # redirect to given commit_id from form if given
183 # redirect to given commit_id from form if given
184 get_commit_id = request.GET.get('at_rev', None)
184 get_commit_id = request.GET.get('at_rev', None)
185 if get_commit_id:
185 if get_commit_id:
186 self.__get_commit_or_redirect(get_commit_id, repo_name)
186 self.__get_commit_or_redirect(get_commit_id, repo_name)
187
187
188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
188 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
189 c.branch = request.GET.get('branch', None)
189 c.branch = request.GET.get('branch', None)
190 c.f_path = f_path
190 c.f_path = f_path
191 c.annotate = annotate
191 c.annotate = annotate
192 # default is false, but .rst/.md files later are autorendered, we can
192 # default is false, but .rst/.md files later are autorendered, we can
193 # overwrite autorendering by setting this GET flag
193 # overwrite autorendering by setting this GET flag
194 c.renderer = rendered or not request.GET.get('no-render', False)
194 c.renderer = rendered or not request.GET.get('no-render', False)
195
195
196 # prev link
196 # prev link
197 try:
197 try:
198 prev_commit = c.commit.prev(c.branch)
198 prev_commit = c.commit.prev(c.branch)
199 c.prev_commit = prev_commit
199 c.prev_commit = prev_commit
200 c.url_prev = url('files_home', repo_name=c.repo_name,
200 c.url_prev = url('files_home', repo_name=c.repo_name,
201 revision=prev_commit.raw_id, f_path=f_path)
201 revision=prev_commit.raw_id, f_path=f_path)
202 if c.branch:
202 if c.branch:
203 c.url_prev += '?branch=%s' % c.branch
203 c.url_prev += '?branch=%s' % c.branch
204 except (CommitDoesNotExistError, VCSError):
204 except (CommitDoesNotExistError, VCSError):
205 c.url_prev = '#'
205 c.url_prev = '#'
206 c.prev_commit = EmptyCommit()
206 c.prev_commit = EmptyCommit()
207
207
208 # next link
208 # next link
209 try:
209 try:
210 next_commit = c.commit.next(c.branch)
210 next_commit = c.commit.next(c.branch)
211 c.next_commit = next_commit
211 c.next_commit = next_commit
212 c.url_next = url('files_home', repo_name=c.repo_name,
212 c.url_next = url('files_home', repo_name=c.repo_name,
213 revision=next_commit.raw_id, f_path=f_path)
213 revision=next_commit.raw_id, f_path=f_path)
214 if c.branch:
214 if c.branch:
215 c.url_next += '?branch=%s' % c.branch
215 c.url_next += '?branch=%s' % c.branch
216 except (CommitDoesNotExistError, VCSError):
216 except (CommitDoesNotExistError, VCSError):
217 c.url_next = '#'
217 c.url_next = '#'
218 c.next_commit = EmptyCommit()
218 c.next_commit = EmptyCommit()
219
219
220 # files or dirs
220 # files or dirs
221 try:
221 try:
222 c.file = c.commit.get_node(f_path)
222 c.file = c.commit.get_node(f_path)
223 c.file_author = True
223 c.file_author = True
224 c.file_tree = ''
224 c.file_tree = ''
225 if c.file.is_file():
225 if c.file.is_file():
226 c.lf_node = c.file.get_largefile_node()
226 c.lf_node = c.file.get_largefile_node()
227
227
228 c.file_source_page = 'true'
228 c.file_source_page = 'true'
229 c.file_last_commit = c.file.last_commit
229 c.file_last_commit = c.file.last_commit
230 if c.file.size < self.cut_off_limit_file:
230 if c.file.size < self.cut_off_limit_file:
231 if c.annotate: # annotation has precedence over renderer
231 if c.annotate: # annotation has precedence over renderer
232 c.annotated_lines = filenode_as_annotated_lines_tokens(
232 c.annotated_lines = filenode_as_annotated_lines_tokens(
233 c.file
233 c.file
234 )
234 )
235 else:
235 else:
236 c.renderer = (
236 c.renderer = (
237 c.renderer and h.renderer_from_filename(c.file.path)
237 c.renderer and h.renderer_from_filename(c.file.path)
238 )
238 )
239 if not c.renderer:
239 if not c.renderer:
240 c.lines = filenode_as_lines_tokens(c.file)
240 c.lines = filenode_as_lines_tokens(c.file)
241
241
242 c.on_branch_head = self._is_valid_head(
242 c.on_branch_head = self._is_valid_head(
243 commit_id, c.rhodecode_repo)
243 commit_id, c.rhodecode_repo)
244
244
245 branch = c.commit.branch if (
245 branch = c.commit.branch if (
246 c.commit.branch and '/' not in c.commit.branch) else None
246 c.commit.branch and '/' not in c.commit.branch) else None
247 c.branch_or_raw_id = branch or c.commit.raw_id
247 c.branch_or_raw_id = branch or c.commit.raw_id
248 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
248 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
249
249
250 author = c.file_last_commit.author
250 author = c.file_last_commit.author
251 c.authors = [(h.email(author),
251 c.authors = [(h.email(author),
252 h.person(author, 'username_or_name_or_email'))]
252 h.person(author, 'username_or_name_or_email'))]
253 else:
253 else:
254 c.file_source_page = 'false'
254 c.file_source_page = 'false'
255 c.authors = []
255 c.authors = []
256 c.file_tree = self._get_tree_at_commit(
256 c.file_tree = self._get_tree_at_commit(
257 repo_name, c.commit.raw_id, f_path)
257 repo_name, c.commit.raw_id, f_path)
258
258
259 except RepositoryError as e:
259 except RepositoryError as e:
260 h.flash(safe_str(e), category='error')
260 h.flash(safe_str(e), category='error')
261 raise HTTPNotFound()
261 raise HTTPNotFound()
262
262
263 if request.environ.get('HTTP_X_PJAX'):
263 if request.environ.get('HTTP_X_PJAX'):
264 return render('files/files_pjax.mako')
264 return render('files/files_pjax.mako')
265
265
266 return render('files/files.mako')
266 return render('files/files.mako')
267
267
268 @LoginRequired()
268 @LoginRequired()
269 @HasRepoPermissionAnyDecorator(
269 @HasRepoPermissionAnyDecorator(
270 'repository.read', 'repository.write', 'repository.admin')
270 'repository.read', 'repository.write', 'repository.admin')
271 def annotate_previous(self, repo_name, revision, f_path):
271 def annotate_previous(self, repo_name, revision, f_path):
272
272
273 commit_id = revision
273 commit_id = revision
274 commit = self.__get_commit_or_redirect(commit_id, repo_name)
274 commit = self.__get_commit_or_redirect(commit_id, repo_name)
275 prev_commit_id = commit.raw_id
275 prev_commit_id = commit.raw_id
276
276
277 f_path = f_path
277 f_path = f_path
278 is_file = False
278 is_file = False
279 try:
279 try:
280 _file = commit.get_node(f_path)
280 _file = commit.get_node(f_path)
281 is_file = _file.is_file()
281 is_file = _file.is_file()
282 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
282 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
283 pass
283 pass
284
284
285 if is_file:
285 if is_file:
286 history = commit.get_file_history(f_path)
286 history = commit.get_file_history(f_path)
287 prev_commit_id = history[1].raw_id \
287 prev_commit_id = history[1].raw_id \
288 if len(history) > 1 else prev_commit_id
288 if len(history) > 1 else prev_commit_id
289
289
290 return redirect(h.url(
290 return redirect(h.url(
291 'files_annotate_home', repo_name=repo_name,
291 'files_annotate_home', repo_name=repo_name,
292 revision=prev_commit_id, f_path=f_path))
292 revision=prev_commit_id, f_path=f_path))
293
293
294 @LoginRequired()
294 @LoginRequired()
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 'repository.admin')
296 'repository.admin')
297 @jsonify
297 @jsonify
298 def history(self, repo_name, revision, f_path):
298 def history(self, repo_name, revision, f_path):
299 commit = self.__get_commit_or_redirect(revision, repo_name)
299 commit = self.__get_commit_or_redirect(revision, repo_name)
300 f_path = f_path
300 f_path = f_path
301 _file = commit.get_node(f_path)
301 _file = commit.get_node(f_path)
302 if _file.is_file():
302 if _file.is_file():
303 file_history, _hist = self._get_node_history(commit, f_path)
303 file_history, _hist = self._get_node_history(commit, f_path)
304
304
305 res = []
305 res = []
306 for obj in file_history:
306 for obj in file_history:
307 res.append({
307 res.append({
308 'text': obj[1],
308 'text': obj[1],
309 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
309 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
310 })
310 })
311
311
312 data = {
312 data = {
313 'more': False,
313 'more': False,
314 'results': res
314 'results': res
315 }
315 }
316 return data
316 return data
317
317
318 @LoginRequired()
318 @LoginRequired()
319 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
319 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
320 'repository.admin')
320 'repository.admin')
321 def authors(self, repo_name, revision, f_path):
321 def authors(self, repo_name, revision, f_path):
322 commit = self.__get_commit_or_redirect(revision, repo_name)
322 commit = self.__get_commit_or_redirect(revision, repo_name)
323 file_node = commit.get_node(f_path)
323 file_node = commit.get_node(f_path)
324 if file_node.is_file():
324 if file_node.is_file():
325 c.file_last_commit = file_node.last_commit
325 c.file_last_commit = file_node.last_commit
326 if request.GET.get('annotate') == '1':
326 if request.GET.get('annotate') == '1':
327 # use _hist from annotation if annotation mode is on
327 # use _hist from annotation if annotation mode is on
328 commit_ids = set(x[1] for x in file_node.annotate)
328 commit_ids = set(x[1] for x in file_node.annotate)
329 _hist = (
329 _hist = (
330 c.rhodecode_repo.get_commit(commit_id)
330 c.rhodecode_repo.get_commit(commit_id)
331 for commit_id in commit_ids)
331 for commit_id in commit_ids)
332 else:
332 else:
333 _f_history, _hist = self._get_node_history(commit, f_path)
333 _f_history, _hist = self._get_node_history(commit, f_path)
334 c.file_author = False
334 c.file_author = False
335 c.authors = []
335 c.authors = []
336 for author in set(commit.author for commit in _hist):
336 for author in set(commit.author for commit in _hist):
337 c.authors.append((
337 c.authors.append((
338 h.email(author),
338 h.email(author),
339 h.person(author, 'username_or_name_or_email')))
339 h.person(author, 'username_or_name_or_email')))
340 return render('files/file_authors_box.mako')
340 return render('files/file_authors_box.mako')
341
341
342 @LoginRequired()
342 @LoginRequired()
343 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
343 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
344 'repository.admin')
344 'repository.admin')
345 def rawfile(self, repo_name, revision, f_path):
345 def rawfile(self, repo_name, revision, f_path):
346 """
346 """
347 Action for download as raw
347 Action for download as raw
348 """
348 """
349 commit = self.__get_commit_or_redirect(revision, repo_name)
349 commit = self.__get_commit_or_redirect(revision, repo_name)
350 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
350 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
351
351
352 if request.GET.get('lf'):
352 if request.GET.get('lf'):
353 # only if lf get flag is passed, we download this file
353 # only if lf get flag is passed, we download this file
354 # as LFS/Largefile
354 # as LFS/Largefile
355 lf_node = file_node.get_largefile_node()
355 lf_node = file_node.get_largefile_node()
356 if lf_node:
356 if lf_node:
357 # overwrite our pointer with the REAL large-file
357 # overwrite our pointer with the REAL large-file
358 file_node = lf_node
358 file_node = lf_node
359
359
360 response.content_disposition = 'attachment; filename=%s' % \
360 response.content_disposition = 'attachment; filename=%s' % \
361 safe_str(f_path.split(Repository.NAME_SEP)[-1])
361 safe_str(f_path.split(Repository.NAME_SEP)[-1])
362
362
363 response.content_type = file_node.mimetype
363 response.content_type = file_node.mimetype
364 charset = self._get_default_encoding()
364 charset = self._get_default_encoding()
365 if charset:
365 if charset:
366 response.charset = charset
366 response.charset = charset
367
367
368 return file_node.content
368 return file_node.content
369
369
370 @LoginRequired()
370 @LoginRequired()
371 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
371 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
372 'repository.admin')
372 'repository.admin')
373 def raw(self, repo_name, revision, f_path):
373 def raw(self, repo_name, revision, f_path):
374 """
374 """
375 Action for show as raw, some mimetypes are "rendered",
375 Action for show as raw, some mimetypes are "rendered",
376 those include images, icons.
376 those include images, icons.
377 """
377 """
378 commit = self.__get_commit_or_redirect(revision, repo_name)
378 commit = self.__get_commit_or_redirect(revision, repo_name)
379 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
379 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
380
380
381 raw_mimetype_mapping = {
381 raw_mimetype_mapping = {
382 # map original mimetype to a mimetype used for "show as raw"
382 # map original mimetype to a mimetype used for "show as raw"
383 # you can also provide a content-disposition to override the
383 # you can also provide a content-disposition to override the
384 # default "attachment" disposition.
384 # default "attachment" disposition.
385 # orig_type: (new_type, new_dispo)
385 # orig_type: (new_type, new_dispo)
386
386
387 # show images inline:
387 # show images inline:
388 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
388 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
389 # for example render an SVG with javascript inside or even render
389 # for example render an SVG with javascript inside or even render
390 # HTML.
390 # HTML.
391 'image/x-icon': ('image/x-icon', 'inline'),
391 'image/x-icon': ('image/x-icon', 'inline'),
392 'image/png': ('image/png', 'inline'),
392 'image/png': ('image/png', 'inline'),
393 'image/gif': ('image/gif', 'inline'),
393 'image/gif': ('image/gif', 'inline'),
394 'image/jpeg': ('image/jpeg', 'inline'),
394 'image/jpeg': ('image/jpeg', 'inline'),
395 'application/pdf': ('application/pdf', 'inline'),
395 'application/pdf': ('application/pdf', 'inline'),
396 }
396 }
397
397
398 mimetype = file_node.mimetype
398 mimetype = file_node.mimetype
399 try:
399 try:
400 mimetype, dispo = raw_mimetype_mapping[mimetype]
400 mimetype, dispo = raw_mimetype_mapping[mimetype]
401 except KeyError:
401 except KeyError:
402 # we don't know anything special about this, handle it safely
402 # we don't know anything special about this, handle it safely
403 if file_node.is_binary:
403 if file_node.is_binary:
404 # do same as download raw for binary files
404 # do same as download raw for binary files
405 mimetype, dispo = 'application/octet-stream', 'attachment'
405 mimetype, dispo = 'application/octet-stream', 'attachment'
406 else:
406 else:
407 # do not just use the original mimetype, but force text/plain,
407 # do not just use the original mimetype, but force text/plain,
408 # otherwise it would serve text/html and that might be unsafe.
408 # otherwise it would serve text/html and that might be unsafe.
409 # Note: underlying vcs library fakes text/plain mimetype if the
409 # Note: underlying vcs library fakes text/plain mimetype if the
410 # mimetype can not be determined and it thinks it is not
410 # mimetype can not be determined and it thinks it is not
411 # binary.This might lead to erroneous text display in some
411 # binary.This might lead to erroneous text display in some
412 # cases, but helps in other cases, like with text files
412 # cases, but helps in other cases, like with text files
413 # without extension.
413 # without extension.
414 mimetype, dispo = 'text/plain', 'inline'
414 mimetype, dispo = 'text/plain', 'inline'
415
415
416 if dispo == 'attachment':
416 if dispo == 'attachment':
417 dispo = 'attachment; filename=%s' % safe_str(
417 dispo = 'attachment; filename=%s' % safe_str(
418 f_path.split(os.sep)[-1])
418 f_path.split(os.sep)[-1])
419
419
420 response.content_disposition = dispo
420 response.content_disposition = dispo
421 response.content_type = mimetype
421 response.content_type = mimetype
422 charset = self._get_default_encoding()
422 charset = self._get_default_encoding()
423 if charset:
423 if charset:
424 response.charset = charset
424 response.charset = charset
425 return file_node.content
425 return file_node.content
426
426
427 @CSRFRequired()
427 @CSRFRequired()
428 @LoginRequired()
428 @LoginRequired()
429 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
429 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
430 def delete(self, repo_name, revision, f_path):
430 def delete(self, repo_name, revision, f_path):
431 commit_id = revision
431 commit_id = revision
432
432
433 repo = c.rhodecode_db_repo
433 repo = c.rhodecode_db_repo
434 if repo.enable_locking and repo.locked[0]:
434 if repo.enable_locking and repo.locked[0]:
435 h.flash(_('This repository has been locked by %s on %s')
435 h.flash(_('This repository has been locked by %s on %s')
436 % (h.person_by_id(repo.locked[0]),
436 % (h.person_by_id(repo.locked[0]),
437 h.format_date(h.time_to_datetime(repo.locked[1]))),
437 h.format_date(h.time_to_datetime(repo.locked[1]))),
438 'warning')
438 'warning')
439 return redirect(h.url('files_home',
439 return redirect(h.url('files_home',
440 repo_name=repo_name, revision='tip'))
440 repo_name=repo_name, revision='tip'))
441
441
442 if not self._is_valid_head(commit_id, repo.scm_instance()):
442 if not self._is_valid_head(commit_id, repo.scm_instance()):
443 h.flash(_('You can only delete files with revision '
443 h.flash(_('You can only delete files with revision '
444 'being a valid branch '), category='warning')
444 'being a valid branch '), category='warning')
445 return redirect(h.url('files_home',
445 return redirect(h.url('files_home',
446 repo_name=repo_name, revision='tip',
446 repo_name=repo_name, revision='tip',
447 f_path=f_path))
447 f_path=f_path))
448
448
449 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
449 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
450 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
450 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
451
451
452 c.default_message = _(
452 c.default_message = _(
453 'Deleted file %s via RhodeCode Enterprise') % (f_path)
453 'Deleted file %s via RhodeCode Enterprise') % (f_path)
454 c.f_path = f_path
454 c.f_path = f_path
455 node_path = f_path
455 node_path = f_path
456 author = c.rhodecode_user.full_contact
456 author = c.rhodecode_user.full_contact
457 message = request.POST.get('message') or c.default_message
457 message = request.POST.get('message') or c.default_message
458 try:
458 try:
459 nodes = {
459 nodes = {
460 node_path: {
460 node_path: {
461 'content': ''
461 'content': ''
462 }
462 }
463 }
463 }
464 self.scm_model.delete_nodes(
464 self.scm_model.delete_nodes(
465 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
465 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
466 message=message,
466 message=message,
467 nodes=nodes,
467 nodes=nodes,
468 parent_commit=c.commit,
468 parent_commit=c.commit,
469 author=author,
469 author=author,
470 )
470 )
471
471
472 h.flash(_('Successfully deleted file %s') % f_path,
472 h.flash(_('Successfully deleted file %s') % f_path,
473 category='success')
473 category='success')
474 except Exception:
474 except Exception:
475 msg = _('Error occurred during commit')
475 msg = _('Error occurred during commit')
476 log.exception(msg)
476 log.exception(msg)
477 h.flash(msg, category='error')
477 h.flash(msg, category='error')
478 return redirect(url('changeset_home',
478 return redirect(url('changeset_home',
479 repo_name=c.repo_name, revision='tip'))
479 repo_name=c.repo_name, revision='tip'))
480
480
481 @LoginRequired()
481 @LoginRequired()
482 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
482 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
483 def delete_home(self, repo_name, revision, f_path):
483 def delete_home(self, repo_name, revision, f_path):
484 commit_id = revision
484 commit_id = revision
485
485
486 repo = c.rhodecode_db_repo
486 repo = c.rhodecode_db_repo
487 if repo.enable_locking and repo.locked[0]:
487 if repo.enable_locking and repo.locked[0]:
488 h.flash(_('This repository has been locked by %s on %s')
488 h.flash(_('This repository has been locked by %s on %s')
489 % (h.person_by_id(repo.locked[0]),
489 % (h.person_by_id(repo.locked[0]),
490 h.format_date(h.time_to_datetime(repo.locked[1]))),
490 h.format_date(h.time_to_datetime(repo.locked[1]))),
491 'warning')
491 'warning')
492 return redirect(h.url('files_home',
492 return redirect(h.url('files_home',
493 repo_name=repo_name, revision='tip'))
493 repo_name=repo_name, revision='tip'))
494
494
495 if not self._is_valid_head(commit_id, repo.scm_instance()):
495 if not self._is_valid_head(commit_id, repo.scm_instance()):
496 h.flash(_('You can only delete files with revision '
496 h.flash(_('You can only delete files with revision '
497 'being a valid branch '), category='warning')
497 'being a valid branch '), category='warning')
498 return redirect(h.url('files_home',
498 return redirect(h.url('files_home',
499 repo_name=repo_name, revision='tip',
499 repo_name=repo_name, revision='tip',
500 f_path=f_path))
500 f_path=f_path))
501
501
502 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
502 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
503 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
503 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
504
504
505 c.default_message = _(
505 c.default_message = _(
506 'Deleted file %s via RhodeCode Enterprise') % (f_path)
506 'Deleted file %s via RhodeCode Enterprise') % (f_path)
507 c.f_path = f_path
507 c.f_path = f_path
508
508
509 return render('files/files_delete.mako')
509 return render('files/files_delete.mako')
510
510
511 @CSRFRequired()
511 @CSRFRequired()
512 @LoginRequired()
512 @LoginRequired()
513 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
513 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
514 def edit(self, repo_name, revision, f_path):
514 def edit(self, repo_name, revision, f_path):
515 commit_id = revision
515 commit_id = revision
516
516
517 repo = c.rhodecode_db_repo
517 repo = c.rhodecode_db_repo
518 if repo.enable_locking and repo.locked[0]:
518 if repo.enable_locking and repo.locked[0]:
519 h.flash(_('This repository has been locked by %s on %s')
519 h.flash(_('This repository has been locked by %s on %s')
520 % (h.person_by_id(repo.locked[0]),
520 % (h.person_by_id(repo.locked[0]),
521 h.format_date(h.time_to_datetime(repo.locked[1]))),
521 h.format_date(h.time_to_datetime(repo.locked[1]))),
522 'warning')
522 'warning')
523 return redirect(h.url('files_home',
523 return redirect(h.url('files_home',
524 repo_name=repo_name, revision='tip'))
524 repo_name=repo_name, revision='tip'))
525
525
526 if not self._is_valid_head(commit_id, repo.scm_instance()):
526 if not self._is_valid_head(commit_id, repo.scm_instance()):
527 h.flash(_('You can only edit files with revision '
527 h.flash(_('You can only edit files with revision '
528 'being a valid branch '), category='warning')
528 'being a valid branch '), category='warning')
529 return redirect(h.url('files_home',
529 return redirect(h.url('files_home',
530 repo_name=repo_name, revision='tip',
530 repo_name=repo_name, revision='tip',
531 f_path=f_path))
531 f_path=f_path))
532
532
533 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
533 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
534 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
534 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
535
535
536 if c.file.is_binary:
536 if c.file.is_binary:
537 return redirect(url('files_home', repo_name=c.repo_name,
537 return redirect(url('files_home', repo_name=c.repo_name,
538 revision=c.commit.raw_id, f_path=f_path))
538 revision=c.commit.raw_id, f_path=f_path))
539 c.default_message = _(
539 c.default_message = _(
540 'Edited file %s via RhodeCode Enterprise') % (f_path)
540 'Edited file %s via RhodeCode Enterprise') % (f_path)
541 c.f_path = f_path
541 c.f_path = f_path
542 old_content = c.file.content
542 old_content = c.file.content
543 sl = old_content.splitlines(1)
543 sl = old_content.splitlines(1)
544 first_line = sl[0] if sl else ''
544 first_line = sl[0] if sl else ''
545
545
546 # modes: 0 - Unix, 1 - Mac, 2 - DOS
546 # modes: 0 - Unix, 1 - Mac, 2 - DOS
547 mode = detect_mode(first_line, 0)
547 mode = detect_mode(first_line, 0)
548 content = convert_line_endings(request.POST.get('content', ''), mode)
548 content = convert_line_endings(request.POST.get('content', ''), mode)
549
549
550 message = request.POST.get('message') or c.default_message
550 message = request.POST.get('message') or c.default_message
551 org_f_path = c.file.unicode_path
551 org_f_path = c.file.unicode_path
552 filename = request.POST['filename']
552 filename = request.POST['filename']
553 org_filename = c.file.name
553 org_filename = c.file.name
554
554
555 if content == old_content and filename == org_filename:
555 if content == old_content and filename == org_filename:
556 h.flash(_('No changes'), category='warning')
556 h.flash(_('No changes'), category='warning')
557 return redirect(url('changeset_home', repo_name=c.repo_name,
557 return redirect(url('changeset_home', repo_name=c.repo_name,
558 revision='tip'))
558 revision='tip'))
559 try:
559 try:
560 mapping = {
560 mapping = {
561 org_f_path: {
561 org_f_path: {
562 'org_filename': org_f_path,
562 'org_filename': org_f_path,
563 'filename': os.path.join(c.file.dir_path, filename),
563 'filename': os.path.join(c.file.dir_path, filename),
564 'content': content,
564 'content': content,
565 'lexer': '',
565 'lexer': '',
566 'op': 'mod',
566 'op': 'mod',
567 }
567 }
568 }
568 }
569
569
570 ScmModel().update_nodes(
570 ScmModel().update_nodes(
571 user=c.rhodecode_user.user_id,
571 user=c.rhodecode_user.user_id,
572 repo=c.rhodecode_db_repo,
572 repo=c.rhodecode_db_repo,
573 message=message,
573 message=message,
574 nodes=mapping,
574 nodes=mapping,
575 parent_commit=c.commit,
575 parent_commit=c.commit,
576 )
576 )
577
577
578 h.flash(_('Successfully committed to %s') % f_path,
578 h.flash(_('Successfully committed to %s') % f_path,
579 category='success')
579 category='success')
580 except Exception:
580 except Exception:
581 msg = _('Error occurred during commit')
581 msg = _('Error occurred during commit')
582 log.exception(msg)
582 log.exception(msg)
583 h.flash(msg, category='error')
583 h.flash(msg, category='error')
584 return redirect(url('changeset_home',
584 return redirect(url('changeset_home',
585 repo_name=c.repo_name, revision='tip'))
585 repo_name=c.repo_name, revision='tip'))
586
586
587 @LoginRequired()
587 @LoginRequired()
588 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
588 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
589 def edit_home(self, repo_name, revision, f_path):
589 def edit_home(self, repo_name, revision, f_path):
590 commit_id = revision
590 commit_id = revision
591
591
592 repo = c.rhodecode_db_repo
592 repo = c.rhodecode_db_repo
593 if repo.enable_locking and repo.locked[0]:
593 if repo.enable_locking and repo.locked[0]:
594 h.flash(_('This repository has been locked by %s on %s')
594 h.flash(_('This repository has been locked by %s on %s')
595 % (h.person_by_id(repo.locked[0]),
595 % (h.person_by_id(repo.locked[0]),
596 h.format_date(h.time_to_datetime(repo.locked[1]))),
596 h.format_date(h.time_to_datetime(repo.locked[1]))),
597 'warning')
597 'warning')
598 return redirect(h.url('files_home',
598 return redirect(h.url('files_home',
599 repo_name=repo_name, revision='tip'))
599 repo_name=repo_name, revision='tip'))
600
600
601 if not self._is_valid_head(commit_id, repo.scm_instance()):
601 if not self._is_valid_head(commit_id, repo.scm_instance()):
602 h.flash(_('You can only edit files with revision '
602 h.flash(_('You can only edit files with revision '
603 'being a valid branch '), category='warning')
603 'being a valid branch '), category='warning')
604 return redirect(h.url('files_home',
604 return redirect(h.url('files_home',
605 repo_name=repo_name, revision='tip',
605 repo_name=repo_name, revision='tip',
606 f_path=f_path))
606 f_path=f_path))
607
607
608 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
608 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
609 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
609 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
610
610
611 if c.file.is_binary:
611 if c.file.is_binary:
612 return redirect(url('files_home', repo_name=c.repo_name,
612 return redirect(url('files_home', repo_name=c.repo_name,
613 revision=c.commit.raw_id, f_path=f_path))
613 revision=c.commit.raw_id, f_path=f_path))
614 c.default_message = _(
614 c.default_message = _(
615 'Edited file %s via RhodeCode Enterprise') % (f_path)
615 'Edited file %s via RhodeCode Enterprise') % (f_path)
616 c.f_path = f_path
616 c.f_path = f_path
617
617
618 return render('files/files_edit.mako')
618 return render('files/files_edit.mako')
619
619
620 def _is_valid_head(self, commit_id, repo):
620 def _is_valid_head(self, commit_id, repo):
621 # check if commit is a branch identifier- basically we cannot
621 # check if commit is a branch identifier- basically we cannot
622 # create multiple heads via file editing
622 # create multiple heads via file editing
623 valid_heads = repo.branches.keys() + repo.branches.values()
623 valid_heads = repo.branches.keys() + repo.branches.values()
624
624
625 if h.is_svn(repo) and not repo.is_empty():
625 if h.is_svn(repo) and not repo.is_empty():
626 # Note: Subversion only has one head, we add it here in case there
626 # Note: Subversion only has one head, we add it here in case there
627 # is no branch matched.
627 # is no branch matched.
628 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
628 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
629
629
630 # check if commit is a branch name or branch hash
630 # check if commit is a branch name or branch hash
631 return commit_id in valid_heads
631 return commit_id in valid_heads
632
632
633 @CSRFRequired()
633 @CSRFRequired()
634 @LoginRequired()
634 @LoginRequired()
635 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
635 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
636 def add(self, repo_name, revision, f_path):
636 def add(self, repo_name, revision, f_path):
637 repo = Repository.get_by_repo_name(repo_name)
637 repo = Repository.get_by_repo_name(repo_name)
638 if repo.enable_locking and repo.locked[0]:
638 if repo.enable_locking and repo.locked[0]:
639 h.flash(_('This repository has been locked by %s on %s')
639 h.flash(_('This repository has been locked by %s on %s')
640 % (h.person_by_id(repo.locked[0]),
640 % (h.person_by_id(repo.locked[0]),
641 h.format_date(h.time_to_datetime(repo.locked[1]))),
641 h.format_date(h.time_to_datetime(repo.locked[1]))),
642 'warning')
642 'warning')
643 return redirect(h.url('files_home',
643 return redirect(h.url('files_home',
644 repo_name=repo_name, revision='tip'))
644 repo_name=repo_name, revision='tip'))
645
645
646 r_post = request.POST
646 r_post = request.POST
647
647
648 c.commit = self.__get_commit_or_redirect(
648 c.commit = self.__get_commit_or_redirect(
649 revision, repo_name, redirect_after=False)
649 revision, repo_name, redirect_after=False)
650 if c.commit is None:
650 if c.commit is None:
651 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
651 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
652 c.default_message = (_('Added file via RhodeCode Enterprise'))
652 c.default_message = (_('Added file via RhodeCode Enterprise'))
653 c.f_path = f_path
653 c.f_path = f_path
654 unix_mode = 0
654 unix_mode = 0
655 content = convert_line_endings(r_post.get('content', ''), unix_mode)
655 content = convert_line_endings(r_post.get('content', ''), unix_mode)
656
656
657 message = r_post.get('message') or c.default_message
657 message = r_post.get('message') or c.default_message
658 filename = r_post.get('filename')
658 filename = r_post.get('filename')
659 location = r_post.get('location', '') # dir location
659 location = r_post.get('location', '') # dir location
660 file_obj = r_post.get('upload_file', None)
660 file_obj = r_post.get('upload_file', None)
661
661
662 if file_obj is not None and hasattr(file_obj, 'filename'):
662 if file_obj is not None and hasattr(file_obj, 'filename'):
663 filename = r_post.get('filename_upload')
663 filename = r_post.get('filename_upload')
664 content = file_obj.file
664 content = file_obj.file
665
665
666 if hasattr(content, 'file'):
666 if hasattr(content, 'file'):
667 # non posix systems store real file under file attr
667 # non posix systems store real file under file attr
668 content = content.file
668 content = content.file
669
669
670 # If there's no commit, redirect to repo summary
670 # If there's no commit, redirect to repo summary
671 if type(c.commit) is EmptyCommit:
671 if type(c.commit) is EmptyCommit:
672 redirect_url = h.route_path('repo_summary', repo_name=c.repo_name)
672 redirect_url = h.route_path('repo_summary', repo_name=c.repo_name)
673 else:
673 else:
674 redirect_url = url("changeset_home", repo_name=c.repo_name,
674 redirect_url = url("changeset_home", repo_name=c.repo_name,
675 revision='tip')
675 revision='tip')
676
676
677 if not filename:
677 if not filename:
678 h.flash(_('No filename'), category='warning')
678 h.flash(_('No filename'), category='warning')
679 return redirect(redirect_url)
679 return redirect(redirect_url)
680
680
681 # extract the location from filename,
681 # extract the location from filename,
682 # allows using foo/bar.txt syntax to create subdirectories
682 # allows using foo/bar.txt syntax to create subdirectories
683 subdir_loc = filename.rsplit('/', 1)
683 subdir_loc = filename.rsplit('/', 1)
684 if len(subdir_loc) == 2:
684 if len(subdir_loc) == 2:
685 location = os.path.join(location, subdir_loc[0])
685 location = os.path.join(location, subdir_loc[0])
686
686
687 # strip all crap out of file, just leave the basename
687 # strip all crap out of file, just leave the basename
688 filename = os.path.basename(filename)
688 filename = os.path.basename(filename)
689 node_path = os.path.join(location, filename)
689 node_path = os.path.join(location, filename)
690 author = c.rhodecode_user.full_contact
690 author = c.rhodecode_user.full_contact
691
691
692 try:
692 try:
693 nodes = {
693 nodes = {
694 node_path: {
694 node_path: {
695 'content': content
695 'content': content
696 }
696 }
697 }
697 }
698 self.scm_model.create_nodes(
698 self.scm_model.create_nodes(
699 user=c.rhodecode_user.user_id,
699 user=c.rhodecode_user.user_id,
700 repo=c.rhodecode_db_repo,
700 repo=c.rhodecode_db_repo,
701 message=message,
701 message=message,
702 nodes=nodes,
702 nodes=nodes,
703 parent_commit=c.commit,
703 parent_commit=c.commit,
704 author=author,
704 author=author,
705 )
705 )
706
706
707 h.flash(_('Successfully committed to %s') % node_path,
707 h.flash(_('Successfully committed to %s') % node_path,
708 category='success')
708 category='success')
709 except NonRelativePathError as e:
709 except NonRelativePathError as e:
710 h.flash(_(
710 h.flash(_(
711 'The location specified must be a relative path and must not '
711 'The location specified must be a relative path and must not '
712 'contain .. in the path'), category='warning')
712 'contain .. in the path'), category='warning')
713 return redirect(url('changeset_home', repo_name=c.repo_name,
713 return redirect(url('changeset_home', repo_name=c.repo_name,
714 revision='tip'))
714 revision='tip'))
715 except (NodeError, NodeAlreadyExistsError) as e:
715 except (NodeError, NodeAlreadyExistsError) as e:
716 h.flash(_(e), category='error')
716 h.flash(_(e), category='error')
717 except Exception:
717 except Exception:
718 msg = _('Error occurred during commit')
718 msg = _('Error occurred during commit')
719 log.exception(msg)
719 log.exception(msg)
720 h.flash(msg, category='error')
720 h.flash(msg, category='error')
721 return redirect(url('changeset_home',
721 return redirect(url('changeset_home',
722 repo_name=c.repo_name, revision='tip'))
722 repo_name=c.repo_name, revision='tip'))
723
723
724 @LoginRequired()
724 @LoginRequired()
725 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
725 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
726 def add_home(self, repo_name, revision, f_path):
726 def add_home(self, repo_name, revision, f_path):
727
727
728 repo = Repository.get_by_repo_name(repo_name)
728 repo = Repository.get_by_repo_name(repo_name)
729 if repo.enable_locking and repo.locked[0]:
729 if repo.enable_locking and repo.locked[0]:
730 h.flash(_('This repository has been locked by %s on %s')
730 h.flash(_('This repository has been locked by %s on %s')
731 % (h.person_by_id(repo.locked[0]),
731 % (h.person_by_id(repo.locked[0]),
732 h.format_date(h.time_to_datetime(repo.locked[1]))),
732 h.format_date(h.time_to_datetime(repo.locked[1]))),
733 'warning')
733 'warning')
734 return redirect(h.url('files_home',
734 return redirect(h.url('files_home',
735 repo_name=repo_name, revision='tip'))
735 repo_name=repo_name, revision='tip'))
736
736
737 c.commit = self.__get_commit_or_redirect(
737 c.commit = self.__get_commit_or_redirect(
738 revision, repo_name, redirect_after=False)
738 revision, repo_name, redirect_after=False)
739 if c.commit is None:
739 if c.commit is None:
740 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
740 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
741 c.default_message = (_('Added file via RhodeCode Enterprise'))
741 c.default_message = (_('Added file via RhodeCode Enterprise'))
742 c.f_path = f_path
742 c.f_path = f_path
743
743
744 return render('files/files_add.mako')
744 return render('files/files_add.mako')
745
745
746 @LoginRequired()
746 @LoginRequired()
747 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
747 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
748 'repository.admin')
748 'repository.admin')
749 def archivefile(self, repo_name, fname):
749 def archivefile(self, repo_name, fname):
750 fileformat = None
750 fileformat = None
751 commit_id = None
751 commit_id = None
752 ext = None
752 ext = None
753 subrepos = request.GET.get('subrepos') == 'true'
753 subrepos = request.GET.get('subrepos') == 'true'
754
754
755 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
755 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
756 archive_spec = fname.split(ext_data[1])
756 archive_spec = fname.split(ext_data[1])
757 if len(archive_spec) == 2 and archive_spec[1] == '':
757 if len(archive_spec) == 2 and archive_spec[1] == '':
758 fileformat = a_type or ext_data[1]
758 fileformat = a_type or ext_data[1]
759 commit_id = archive_spec[0]
759 commit_id = archive_spec[0]
760 ext = ext_data[1]
760 ext = ext_data[1]
761
761
762 dbrepo = RepoModel().get_by_repo_name(repo_name)
762 dbrepo = RepoModel().get_by_repo_name(repo_name)
763 if not dbrepo.enable_downloads:
763 if not dbrepo.enable_downloads:
764 return _('Downloads disabled')
764 return _('Downloads disabled')
765
765
766 try:
766 try:
767 commit = c.rhodecode_repo.get_commit(commit_id)
767 commit = c.rhodecode_repo.get_commit(commit_id)
768 content_type = settings.ARCHIVE_SPECS[fileformat][0]
768 content_type = settings.ARCHIVE_SPECS[fileformat][0]
769 except CommitDoesNotExistError:
769 except CommitDoesNotExistError:
770 return _('Unknown revision %s') % commit_id
770 return _('Unknown revision %s') % commit_id
771 except EmptyRepositoryError:
771 except EmptyRepositoryError:
772 return _('Empty repository')
772 return _('Empty repository')
773 except KeyError:
773 except KeyError:
774 return _('Unknown archive type')
774 return _('Unknown archive type')
775
775
776 # archive cache
776 # archive cache
777 from rhodecode import CONFIG
777 from rhodecode import CONFIG
778
778
779 archive_name = '%s-%s%s%s' % (
779 archive_name = '%s-%s%s%s' % (
780 safe_str(repo_name.replace('/', '_')),
780 safe_str(repo_name.replace('/', '_')),
781 '-sub' if subrepos else '',
781 '-sub' if subrepos else '',
782 safe_str(commit.short_id), ext)
782 safe_str(commit.short_id), ext)
783
783
784 use_cached_archive = False
784 use_cached_archive = False
785 archive_cache_enabled = CONFIG.get(
785 archive_cache_enabled = CONFIG.get(
786 'archive_cache_dir') and not request.GET.get('no_cache')
786 'archive_cache_dir') and not request.GET.get('no_cache')
787
787
788 if archive_cache_enabled:
788 if archive_cache_enabled:
789 # check if we it's ok to write
789 # check if we it's ok to write
790 if not os.path.isdir(CONFIG['archive_cache_dir']):
790 if not os.path.isdir(CONFIG['archive_cache_dir']):
791 os.makedirs(CONFIG['archive_cache_dir'])
791 os.makedirs(CONFIG['archive_cache_dir'])
792 cached_archive_path = os.path.join(
792 cached_archive_path = os.path.join(
793 CONFIG['archive_cache_dir'], archive_name)
793 CONFIG['archive_cache_dir'], archive_name)
794 if os.path.isfile(cached_archive_path):
794 if os.path.isfile(cached_archive_path):
795 log.debug('Found cached archive in %s', cached_archive_path)
795 log.debug('Found cached archive in %s', cached_archive_path)
796 fd, archive = None, cached_archive_path
796 fd, archive = None, cached_archive_path
797 use_cached_archive = True
797 use_cached_archive = True
798 else:
798 else:
799 log.debug('Archive %s is not yet cached', archive_name)
799 log.debug('Archive %s is not yet cached', archive_name)
800
800
801 if not use_cached_archive:
801 if not use_cached_archive:
802 # generate new archive
802 # generate new archive
803 fd, archive = tempfile.mkstemp()
803 fd, archive = tempfile.mkstemp()
804 log.debug('Creating new temp archive in %s' % (archive,))
804 log.debug('Creating new temp archive in %s' % (archive,))
805 try:
805 try:
806 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
806 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
807 except ImproperArchiveTypeError:
807 except ImproperArchiveTypeError:
808 return _('Unknown archive type')
808 return _('Unknown archive type')
809 if archive_cache_enabled:
809 if archive_cache_enabled:
810 # if we generated the archive and we have cache enabled
810 # if we generated the archive and we have cache enabled
811 # let's use this for future
811 # let's use this for future
812 log.debug('Storing new archive in %s' % (cached_archive_path,))
812 log.debug('Storing new archive in %s' % (cached_archive_path,))
813 shutil.move(archive, cached_archive_path)
813 shutil.move(archive, cached_archive_path)
814 archive = cached_archive_path
814 archive = cached_archive_path
815
815
816 # store download action
816 # store download action
817 audit_logger.store(
817 audit_logger.store_web(
818 action='repo.archive.download',
818 action='repo.archive.download',
819 action_data={'user_agent': request.user_agent,
819 action_data={'user_agent': request.user_agent,
820 'archive_name': archive_name,
820 'archive_name': archive_name,
821 'archive_spec': fname,
821 'archive_spec': fname,
822 'archive_cached': use_cached_archive},
822 'archive_cached': use_cached_archive},
823 user=c.rhodecode_user,
823 user=c.rhodecode_user,
824 repo=dbrepo,
824 repo=dbrepo,
825 commit=True
825 commit=True
826 )
826 )
827
827
828 response.content_disposition = str(
828 response.content_disposition = str(
829 'attachment; filename=%s' % archive_name)
829 'attachment; filename=%s' % archive_name)
830 response.content_type = str(content_type)
830 response.content_type = str(content_type)
831
831
832 def get_chunked_archive(archive):
832 def get_chunked_archive(archive):
833 with open(archive, 'rb') as stream:
833 with open(archive, 'rb') as stream:
834 while True:
834 while True:
835 data = stream.read(16 * 1024)
835 data = stream.read(16 * 1024)
836 if not data:
836 if not data:
837 if fd: # fd means we used temporary file
837 if fd: # fd means we used temporary file
838 os.close(fd)
838 os.close(fd)
839 if not archive_cache_enabled:
839 if not archive_cache_enabled:
840 log.debug('Destroying temp archive %s', archive)
840 log.debug('Destroying temp archive %s', archive)
841 os.remove(archive)
841 os.remove(archive)
842 break
842 break
843 yield data
843 yield data
844
844
845 return get_chunked_archive(archive)
845 return get_chunked_archive(archive)
846
846
847 @LoginRequired()
847 @LoginRequired()
848 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
848 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
849 'repository.admin')
849 'repository.admin')
850 def diff(self, repo_name, f_path):
850 def diff(self, repo_name, f_path):
851
851
852 c.action = request.GET.get('diff')
852 c.action = request.GET.get('diff')
853 diff1 = request.GET.get('diff1', '')
853 diff1 = request.GET.get('diff1', '')
854 diff2 = request.GET.get('diff2', '')
854 diff2 = request.GET.get('diff2', '')
855
855
856 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
856 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
857
857
858 ignore_whitespace = str2bool(request.GET.get('ignorews'))
858 ignore_whitespace = str2bool(request.GET.get('ignorews'))
859 line_context = request.GET.get('context', 3)
859 line_context = request.GET.get('context', 3)
860
860
861 if not any((diff1, diff2)):
861 if not any((diff1, diff2)):
862 h.flash(
862 h.flash(
863 'Need query parameter "diff1" or "diff2" to generate a diff.',
863 'Need query parameter "diff1" or "diff2" to generate a diff.',
864 category='error')
864 category='error')
865 raise HTTPBadRequest()
865 raise HTTPBadRequest()
866
866
867 if c.action not in ['download', 'raw']:
867 if c.action not in ['download', 'raw']:
868 # redirect to new view if we render diff
868 # redirect to new view if we render diff
869 return redirect(
869 return redirect(
870 url('compare_url', repo_name=repo_name,
870 url('compare_url', repo_name=repo_name,
871 source_ref_type='rev',
871 source_ref_type='rev',
872 source_ref=diff1,
872 source_ref=diff1,
873 target_repo=c.repo_name,
873 target_repo=c.repo_name,
874 target_ref_type='rev',
874 target_ref_type='rev',
875 target_ref=diff2,
875 target_ref=diff2,
876 f_path=f_path))
876 f_path=f_path))
877
877
878 try:
878 try:
879 node1 = self._get_file_node(diff1, path1)
879 node1 = self._get_file_node(diff1, path1)
880 node2 = self._get_file_node(diff2, f_path)
880 node2 = self._get_file_node(diff2, f_path)
881 except (RepositoryError, NodeError):
881 except (RepositoryError, NodeError):
882 log.exception("Exception while trying to get node from repository")
882 log.exception("Exception while trying to get node from repository")
883 return redirect(url(
883 return redirect(url(
884 'files_home', repo_name=c.repo_name, f_path=f_path))
884 'files_home', repo_name=c.repo_name, f_path=f_path))
885
885
886 if all(isinstance(node.commit, EmptyCommit)
886 if all(isinstance(node.commit, EmptyCommit)
887 for node in (node1, node2)):
887 for node in (node1, node2)):
888 raise HTTPNotFound
888 raise HTTPNotFound
889
889
890 c.commit_1 = node1.commit
890 c.commit_1 = node1.commit
891 c.commit_2 = node2.commit
891 c.commit_2 = node2.commit
892
892
893 if c.action == 'download':
893 if c.action == 'download':
894 _diff = diffs.get_gitdiff(node1, node2,
894 _diff = diffs.get_gitdiff(node1, node2,
895 ignore_whitespace=ignore_whitespace,
895 ignore_whitespace=ignore_whitespace,
896 context=line_context)
896 context=line_context)
897 diff = diffs.DiffProcessor(_diff, format='gitdiff')
897 diff = diffs.DiffProcessor(_diff, format='gitdiff')
898
898
899 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
899 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
900 response.content_type = 'text/plain'
900 response.content_type = 'text/plain'
901 response.content_disposition = (
901 response.content_disposition = (
902 'attachment; filename=%s' % (diff_name,)
902 'attachment; filename=%s' % (diff_name,)
903 )
903 )
904 charset = self._get_default_encoding()
904 charset = self._get_default_encoding()
905 if charset:
905 if charset:
906 response.charset = charset
906 response.charset = charset
907 return diff.as_raw()
907 return diff.as_raw()
908
908
909 elif c.action == 'raw':
909 elif c.action == 'raw':
910 _diff = diffs.get_gitdiff(node1, node2,
910 _diff = diffs.get_gitdiff(node1, node2,
911 ignore_whitespace=ignore_whitespace,
911 ignore_whitespace=ignore_whitespace,
912 context=line_context)
912 context=line_context)
913 diff = diffs.DiffProcessor(_diff, format='gitdiff')
913 diff = diffs.DiffProcessor(_diff, format='gitdiff')
914 response.content_type = 'text/plain'
914 response.content_type = 'text/plain'
915 charset = self._get_default_encoding()
915 charset = self._get_default_encoding()
916 if charset:
916 if charset:
917 response.charset = charset
917 response.charset = charset
918 return diff.as_raw()
918 return diff.as_raw()
919
919
920 else:
920 else:
921 return redirect(
921 return redirect(
922 url('compare_url', repo_name=repo_name,
922 url('compare_url', repo_name=repo_name,
923 source_ref_type='rev',
923 source_ref_type='rev',
924 source_ref=diff1,
924 source_ref=diff1,
925 target_repo=c.repo_name,
925 target_repo=c.repo_name,
926 target_ref_type='rev',
926 target_ref_type='rev',
927 target_ref=diff2,
927 target_ref=diff2,
928 f_path=f_path))
928 f_path=f_path))
929
929
930 @LoginRequired()
930 @LoginRequired()
931 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
931 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
932 'repository.admin')
932 'repository.admin')
933 def diff_2way(self, repo_name, f_path):
933 def diff_2way(self, repo_name, f_path):
934 """
934 """
935 Kept only to make OLD links work
935 Kept only to make OLD links work
936 """
936 """
937 diff1 = request.GET.get('diff1', '')
937 diff1 = request.GET.get('diff1', '')
938 diff2 = request.GET.get('diff2', '')
938 diff2 = request.GET.get('diff2', '')
939
939
940 if not any((diff1, diff2)):
940 if not any((diff1, diff2)):
941 h.flash(
941 h.flash(
942 'Need query parameter "diff1" or "diff2" to generate a diff.',
942 'Need query parameter "diff1" or "diff2" to generate a diff.',
943 category='error')
943 category='error')
944 raise HTTPBadRequest()
944 raise HTTPBadRequest()
945
945
946 return redirect(
946 return redirect(
947 url('compare_url', repo_name=repo_name,
947 url('compare_url', repo_name=repo_name,
948 source_ref_type='rev',
948 source_ref_type='rev',
949 source_ref=diff1,
949 source_ref=diff1,
950 target_repo=c.repo_name,
950 target_repo=c.repo_name,
951 target_ref_type='rev',
951 target_ref_type='rev',
952 target_ref=diff2,
952 target_ref=diff2,
953 f_path=f_path,
953 f_path=f_path,
954 diffmode='sideside'))
954 diffmode='sideside'))
955
955
956 def _get_file_node(self, commit_id, f_path):
956 def _get_file_node(self, commit_id, f_path):
957 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
957 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
958 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
958 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
959 try:
959 try:
960 node = commit.get_node(f_path)
960 node = commit.get_node(f_path)
961 if node.is_dir():
961 if node.is_dir():
962 raise NodeError('%s path is a %s not a file'
962 raise NodeError('%s path is a %s not a file'
963 % (node, type(node)))
963 % (node, type(node)))
964 except NodeDoesNotExistError:
964 except NodeDoesNotExistError:
965 commit = EmptyCommit(
965 commit = EmptyCommit(
966 commit_id=commit_id,
966 commit_id=commit_id,
967 idx=commit.idx,
967 idx=commit.idx,
968 repo=commit.repository,
968 repo=commit.repository,
969 alias=commit.repository.alias,
969 alias=commit.repository.alias,
970 message=commit.message,
970 message=commit.message,
971 author=commit.author,
971 author=commit.author,
972 date=commit.date)
972 date=commit.date)
973 node = FileNode(f_path, '', commit=commit)
973 node = FileNode(f_path, '', commit=commit)
974 else:
974 else:
975 commit = EmptyCommit(
975 commit = EmptyCommit(
976 repo=c.rhodecode_repo,
976 repo=c.rhodecode_repo,
977 alias=c.rhodecode_repo.alias)
977 alias=c.rhodecode_repo.alias)
978 node = FileNode(f_path, '', commit=commit)
978 node = FileNode(f_path, '', commit=commit)
979 return node
979 return node
980
980
981 def _get_node_history(self, commit, f_path, commits=None):
981 def _get_node_history(self, commit, f_path, commits=None):
982 """
982 """
983 get commit history for given node
983 get commit history for given node
984
984
985 :param commit: commit to calculate history
985 :param commit: commit to calculate history
986 :param f_path: path for node to calculate history for
986 :param f_path: path for node to calculate history for
987 :param commits: if passed don't calculate history and take
987 :param commits: if passed don't calculate history and take
988 commits defined in this list
988 commits defined in this list
989 """
989 """
990 # calculate history based on tip
990 # calculate history based on tip
991 tip = c.rhodecode_repo.get_commit()
991 tip = c.rhodecode_repo.get_commit()
992 if commits is None:
992 if commits is None:
993 pre_load = ["author", "branch"]
993 pre_load = ["author", "branch"]
994 try:
994 try:
995 commits = tip.get_file_history(f_path, pre_load=pre_load)
995 commits = tip.get_file_history(f_path, pre_load=pre_load)
996 except (NodeDoesNotExistError, CommitError):
996 except (NodeDoesNotExistError, CommitError):
997 # this node is not present at tip!
997 # this node is not present at tip!
998 commits = commit.get_file_history(f_path, pre_load=pre_load)
998 commits = commit.get_file_history(f_path, pre_load=pre_load)
999
999
1000 history = []
1000 history = []
1001 commits_group = ([], _("Changesets"))
1001 commits_group = ([], _("Changesets"))
1002 for commit in commits:
1002 for commit in commits:
1003 branch = ' (%s)' % commit.branch if commit.branch else ''
1003 branch = ' (%s)' % commit.branch if commit.branch else ''
1004 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1004 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1005 commits_group[0].append((commit.raw_id, n_desc,))
1005 commits_group[0].append((commit.raw_id, n_desc,))
1006 history.append(commits_group)
1006 history.append(commits_group)
1007
1007
1008 symbolic_reference = self._symbolic_reference
1008 symbolic_reference = self._symbolic_reference
1009
1009
1010 if c.rhodecode_repo.alias == 'svn':
1010 if c.rhodecode_repo.alias == 'svn':
1011 adjusted_f_path = self._adjust_file_path_for_svn(
1011 adjusted_f_path = self._adjust_file_path_for_svn(
1012 f_path, c.rhodecode_repo)
1012 f_path, c.rhodecode_repo)
1013 if adjusted_f_path != f_path:
1013 if adjusted_f_path != f_path:
1014 log.debug(
1014 log.debug(
1015 'Recognized svn tag or branch in file "%s", using svn '
1015 'Recognized svn tag or branch in file "%s", using svn '
1016 'specific symbolic references', f_path)
1016 'specific symbolic references', f_path)
1017 f_path = adjusted_f_path
1017 f_path = adjusted_f_path
1018 symbolic_reference = self._symbolic_reference_svn
1018 symbolic_reference = self._symbolic_reference_svn
1019
1019
1020 branches = self._create_references(
1020 branches = self._create_references(
1021 c.rhodecode_repo.branches, symbolic_reference, f_path)
1021 c.rhodecode_repo.branches, symbolic_reference, f_path)
1022 branches_group = (branches, _("Branches"))
1022 branches_group = (branches, _("Branches"))
1023
1023
1024 tags = self._create_references(
1024 tags = self._create_references(
1025 c.rhodecode_repo.tags, symbolic_reference, f_path)
1025 c.rhodecode_repo.tags, symbolic_reference, f_path)
1026 tags_group = (tags, _("Tags"))
1026 tags_group = (tags, _("Tags"))
1027
1027
1028 history.append(branches_group)
1028 history.append(branches_group)
1029 history.append(tags_group)
1029 history.append(tags_group)
1030
1030
1031 return history, commits
1031 return history, commits
1032
1032
1033 def _adjust_file_path_for_svn(self, f_path, repo):
1033 def _adjust_file_path_for_svn(self, f_path, repo):
1034 """
1034 """
1035 Computes the relative path of `f_path`.
1035 Computes the relative path of `f_path`.
1036
1036
1037 This is mainly based on prefix matching of the recognized tags and
1037 This is mainly based on prefix matching of the recognized tags and
1038 branches in the underlying repository.
1038 branches in the underlying repository.
1039 """
1039 """
1040 tags_and_branches = itertools.chain(
1040 tags_and_branches = itertools.chain(
1041 repo.branches.iterkeys(),
1041 repo.branches.iterkeys(),
1042 repo.tags.iterkeys())
1042 repo.tags.iterkeys())
1043 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1043 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1044
1044
1045 for name in tags_and_branches:
1045 for name in tags_and_branches:
1046 if f_path.startswith(name + '/'):
1046 if f_path.startswith(name + '/'):
1047 f_path = vcspath.relpath(f_path, name)
1047 f_path = vcspath.relpath(f_path, name)
1048 break
1048 break
1049 return f_path
1049 return f_path
1050
1050
1051 def _create_references(
1051 def _create_references(
1052 self, branches_or_tags, symbolic_reference, f_path):
1052 self, branches_or_tags, symbolic_reference, f_path):
1053 items = []
1053 items = []
1054 for name, commit_id in branches_or_tags.items():
1054 for name, commit_id in branches_or_tags.items():
1055 sym_ref = symbolic_reference(commit_id, name, f_path)
1055 sym_ref = symbolic_reference(commit_id, name, f_path)
1056 items.append((sym_ref, name))
1056 items.append((sym_ref, name))
1057 return items
1057 return items
1058
1058
1059 def _symbolic_reference(self, commit_id, name, f_path):
1059 def _symbolic_reference(self, commit_id, name, f_path):
1060 return commit_id
1060 return commit_id
1061
1061
1062 def _symbolic_reference_svn(self, commit_id, name, f_path):
1062 def _symbolic_reference_svn(self, commit_id, name, f_path):
1063 new_f_path = vcspath.join(name, f_path)
1063 new_f_path = vcspath.join(name, f_path)
1064 return u'%s@%s' % (new_f_path, commit_id)
1064 return u'%s@%s' % (new_f_path, commit_id)
1065
1065
1066 @LoginRequired()
1066 @LoginRequired()
1067 @XHRRequired()
1067 @XHRRequired()
1068 @HasRepoPermissionAnyDecorator(
1068 @HasRepoPermissionAnyDecorator(
1069 'repository.read', 'repository.write', 'repository.admin')
1069 'repository.read', 'repository.write', 'repository.admin')
1070 @jsonify
1070 @jsonify
1071 def nodelist(self, repo_name, revision, f_path):
1071 def nodelist(self, repo_name, revision, f_path):
1072 commit = self.__get_commit_or_redirect(revision, repo_name)
1072 commit = self.__get_commit_or_redirect(revision, repo_name)
1073
1073
1074 metadata = self._get_nodelist_at_commit(
1074 metadata = self._get_nodelist_at_commit(
1075 repo_name, commit.raw_id, f_path)
1075 repo_name, commit.raw_id, f_path)
1076 return {'nodes': metadata}
1076 return {'nodes': metadata}
1077
1077
1078 @LoginRequired()
1078 @LoginRequired()
1079 @XHRRequired()
1079 @XHRRequired()
1080 @HasRepoPermissionAnyDecorator(
1080 @HasRepoPermissionAnyDecorator(
1081 'repository.read', 'repository.write', 'repository.admin')
1081 'repository.read', 'repository.write', 'repository.admin')
1082 def nodetree_full(self, repo_name, commit_id, f_path):
1082 def nodetree_full(self, repo_name, commit_id, f_path):
1083 """
1083 """
1084 Returns rendered html of file tree that contains commit date,
1084 Returns rendered html of file tree that contains commit date,
1085 author, revision for the specified combination of
1085 author, revision for the specified combination of
1086 repo, commit_id and file path
1086 repo, commit_id and file path
1087
1087
1088 :param repo_name: name of the repository
1088 :param repo_name: name of the repository
1089 :param commit_id: commit_id of file tree
1089 :param commit_id: commit_id of file tree
1090 :param f_path: file path of the requested directory
1090 :param f_path: file path of the requested directory
1091 """
1091 """
1092
1092
1093 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1093 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1094 try:
1094 try:
1095 dir_node = commit.get_node(f_path)
1095 dir_node = commit.get_node(f_path)
1096 except RepositoryError as e:
1096 except RepositoryError as e:
1097 return 'error {}'.format(safe_str(e))
1097 return 'error {}'.format(safe_str(e))
1098
1098
1099 if dir_node.is_file():
1099 if dir_node.is_file():
1100 return ''
1100 return ''
1101
1101
1102 c.file = dir_node
1102 c.file = dir_node
1103 c.commit = commit
1103 c.commit = commit
1104
1104
1105 # using force=True here, make a little trick. We flush the cache and
1105 # using force=True here, make a little trick. We flush the cache and
1106 # compute it using the same key as without full_load, so the fully
1106 # compute it using the same key as without full_load, so the fully
1107 # loaded cached tree is now returned instead of partial
1107 # loaded cached tree is now returned instead of partial
1108 return self._get_tree_at_commit(
1108 return self._get_tree_at_commit(
1109 repo_name, commit.raw_id, dir_node.path, full_load=True,
1109 repo_name, commit.raw_id, dir_node.path, full_load=True,
1110 force=True)
1110 force=True)
@@ -1,240 +1,240 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23
23
24 from rhodecode.model import meta
24 from rhodecode.model import meta
25 from rhodecode.model.db import User, UserLog, Repository
25 from rhodecode.model.db import User, UserLog, Repository
26
26
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30 # action as key, and expected action_data as value
30 # action as key, and expected action_data as value
31 ACTIONS = {
31 ACTIONS = {
32 'user.login.success': {'user_agent': ''},
32 'user.login.success': {'user_agent': ''},
33 'user.login.failure': {'user_agent': ''},
33 'user.login.failure': {'user_agent': ''},
34 'user.logout': {'user_agent': ''},
34 'user.logout': {'user_agent': ''},
35 'user.password.reset_request': {},
35 'user.password.reset_request': {},
36 'user.push': {'user_agent': '', 'commit_ids': []},
36 'user.push': {'user_agent': '', 'commit_ids': []},
37 'user.pull': {'user_agent': ''},
37 'user.pull': {'user_agent': ''},
38
38
39 'user.create': {'data': {}},
39 'user.create': {'data': {}},
40 'user.delete': {'old_data': {}},
40 'user.delete': {'old_data': {}},
41 'user.edit': {'old_data': {}},
41 'user.edit': {'old_data': {}},
42 'user.edit.permissions': {},
42 'user.edit.permissions': {},
43 'user.edit.ip.add': {},
43 'user.edit.ip.add': {},
44 'user.edit.ip.delete': {},
44 'user.edit.ip.delete': {},
45 'user.edit.token.add': {},
45 'user.edit.token.add': {},
46 'user.edit.token.delete': {},
46 'user.edit.token.delete': {},
47 'user.edit.email.add': {},
47 'user.edit.email.add': {},
48 'user.edit.email.delete': {},
48 'user.edit.email.delete': {},
49 'user.edit.password_reset.enabled': {},
49 'user.edit.password_reset.enabled': {},
50 'user.edit.password_reset.disabled': {},
50 'user.edit.password_reset.disabled': {},
51
51
52 'user_group.create': {'data': {}},
52 'user_group.create': {'data': {}},
53 'user_group.delete': {'old_data': {}},
53 'user_group.delete': {'old_data': {}},
54 'user_group.edit': {'old_data': {}},
54 'user_group.edit': {'old_data': {}},
55 'user_group.edit.permissions': {},
55 'user_group.edit.permissions': {},
56 'user_group.edit.member.add': {},
56 'user_group.edit.member.add': {},
57 'user_group.edit.member.delete': {},
57 'user_group.edit.member.delete': {},
58
58
59 'repo.create': {'data': {}},
59 'repo.create': {'data': {}},
60 'repo.fork': {'data': {}},
60 'repo.fork': {'data': {}},
61 'repo.edit': {'old_data': {}},
61 'repo.edit': {'old_data': {}},
62 'repo.edit.permissions': {},
62 'repo.edit.permissions': {},
63 'repo.delete': {'old_data': {}},
63 'repo.delete': {'old_data': {}},
64 'repo.commit.strip': {},
64 'repo.commit.strip': {},
65 'repo.archive.download': {},
65 'repo.archive.download': {},
66
66
67 'repo_group.create': {'data': {}},
67 'repo_group.create': {'data': {}},
68 'repo_group.edit': {'old_data': {}},
68 'repo_group.edit': {'old_data': {}},
69 'repo_group.edit.permissions': {},
69 'repo_group.edit.permissions': {},
70 'repo_group.delete': {'old_data': {}},
70 'repo_group.delete': {'old_data': {}},
71 }
71 }
72
72
73 SOURCE_WEB = 'source_web'
73 SOURCE_WEB = 'source_web'
74 SOURCE_API = 'source_api'
74 SOURCE_API = 'source_api'
75
75
76
76
77 class UserWrap(object):
77 class UserWrap(object):
78 """
78 """
79 Fake object used to imitate AuthUser
79 Fake object used to imitate AuthUser
80 """
80 """
81
81
82 def __init__(self, user_id=None, username=None, ip_addr=None):
82 def __init__(self, user_id=None, username=None, ip_addr=None):
83 self.user_id = user_id
83 self.user_id = user_id
84 self.username = username
84 self.username = username
85 self.ip_addr = ip_addr
85 self.ip_addr = ip_addr
86
86
87
87
88 class RepoWrap(object):
88 class RepoWrap(object):
89 """
89 """
90 Fake object used to imitate RepoObject that audit logger requires
90 Fake object used to imitate RepoObject that audit logger requires
91 """
91 """
92
92
93 def __init__(self, repo_id=None, repo_name=None):
93 def __init__(self, repo_id=None, repo_name=None):
94 self.repo_id = repo_id
94 self.repo_id = repo_id
95 self.repo_name = repo_name
95 self.repo_name = repo_name
96
96
97
97
98 def _store_log(action_name, action_data, user_id, username, user_data,
98 def _store_log(action_name, action_data, user_id, username, user_data,
99 ip_address, repository_id, repository_name):
99 ip_address, repository_id, repository_name):
100 user_log = UserLog()
100 user_log = UserLog()
101 user_log.version = UserLog.VERSION_2
101 user_log.version = UserLog.VERSION_2
102
102
103 user_log.action = action_name
103 user_log.action = action_name
104 user_log.action_data = action_data
104 user_log.action_data = action_data
105
105
106 user_log.user_ip = ip_address
106 user_log.user_ip = ip_address
107
107
108 user_log.user_id = user_id
108 user_log.user_id = user_id
109 user_log.username = username
109 user_log.username = username
110 user_log.user_data = user_data
110 user_log.user_data = user_data
111
111
112 user_log.repository_id = repository_id
112 user_log.repository_id = repository_id
113 user_log.repository_name = repository_name
113 user_log.repository_name = repository_name
114
114
115 user_log.action_date = datetime.datetime.now()
115 user_log.action_date = datetime.datetime.now()
116
116
117 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
117 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
118 action_name, user_id, username, ip_address)
118 action_name, user_id, username, ip_address)
119
119
120 return user_log
120 return user_log
121
121
122
122
123 def store_web(*args, **kwargs):
123 def store_web(*args, **kwargs):
124 if 'action_data' not in kwargs:
124 if 'action_data' not in kwargs:
125 kwargs['action_data'] = {}
125 kwargs['action_data'] = {}
126 kwargs['action_data'].update({
126 kwargs['action_data'].update({
127 'source': SOURCE_WEB
127 'source': SOURCE_WEB
128 })
128 })
129 return store(*args, **kwargs)
129 return store(*args, **kwargs)
130
130
131
131
132 def store_api(*args, **kwargs):
132 def store_api(*args, **kwargs):
133 if 'action_data' not in kwargs:
133 if 'action_data' not in kwargs:
134 kwargs['action_data'] = {}
134 kwargs['action_data'] = {}
135 kwargs['action_data'].update({
135 kwargs['action_data'].update({
136 'source': SOURCE_API
136 'source': SOURCE_API
137 })
137 })
138 return store(*args, **kwargs)
138 return store(*args, **kwargs)
139
139
140
140
141 def store(action, user, action_data=None, user_data=None, ip_addr=None,
141 def store(action, user, action_data=None, user_data=None, ip_addr=None,
142 repo=None, sa_session=None, commit=False):
142 repo=None, sa_session=None, commit=False):
143 """
143 """
144 Audit logger for various actions made by users, typically this
144 Audit logger for various actions made by users, typically this
145 results in a call such::
145 results in a call such::
146
146
147 from rhodecode.lib import audit_logger
147 from rhodecode.lib import audit_logger
148
148
149 audit_logger.store(
149 audit_logger.store(
150 action='repo.edit', user=self._rhodecode_user)
150 action='repo.edit', user=self._rhodecode_user)
151 audit_logger.store(
151 audit_logger.store(
152 action='repo.delete', action_data={'repo_data': repo_data},
152 action='repo.delete', action_data={'data': repo_data},
153 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
153 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
154
154
155 # repo action
155 # repo action
156 audit_logger.store(
156 audit_logger.store(
157 action='repo.delete',
157 action='repo.delete',
158 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
158 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
159 repo=audit_logger.RepoWrap(repo_name='some-repo'))
159 repo=audit_logger.RepoWrap(repo_name='some-repo'))
160
160
161 # repo action, when we know and have the repository object already
161 # repo action, when we know and have the repository object already
162 audit_logger.store(
162 audit_logger.store(
163 action='repo.delete',
163 action='repo.delete',
164 action_data={'source': audit_logger.SOURCE_WEB, },
164 action_data={'source': audit_logger.SOURCE_WEB, },
165 user=self._rhodecode_user,
165 user=self._rhodecode_user,
166 repo=repo_object)
166 repo=repo_object)
167
167
168 # alternative wrapper to the above
168 # alternative wrapper to the above
169 audit_logger.store_web(
169 audit_logger.store_web(
170 action='repo.delete',
170 action='repo.delete',
171 action_data={},
171 action_data={},
172 user=self._rhodecode_user,
172 user=self._rhodecode_user,
173 repo=repo_object)
173 repo=repo_object)
174
174
175 # without an user ?
175 # without an user ?
176 audit_logger.store(
176 audit_logger.store(
177 action='user.login.failure',
177 action='user.login.failure',
178 user=audit_logger.UserWrap(
178 user=audit_logger.UserWrap(
179 username=self.request.params.get('username'),
179 username=self.request.params.get('username'),
180 ip_addr=self.request.remote_addr))
180 ip_addr=self.request.remote_addr))
181
181
182 """
182 """
183 from rhodecode.lib.utils2 import safe_unicode
183 from rhodecode.lib.utils2 import safe_unicode
184 from rhodecode.lib.auth import AuthUser
184 from rhodecode.lib.auth import AuthUser
185
185
186 action_spec = ACTIONS.get(action, None)
186 action_spec = ACTIONS.get(action, None)
187 if action_spec is None:
187 if action_spec is None:
188 raise ValueError('Action `{}` is not supported'.format(action))
188 raise ValueError('Action `{}` is not supported'.format(action))
189
189
190 if not sa_session:
190 if not sa_session:
191 sa_session = meta.Session()
191 sa_session = meta.Session()
192
192
193 try:
193 try:
194 username = getattr(user, 'username', None)
194 username = getattr(user, 'username', None)
195 if not username:
195 if not username:
196 pass
196 pass
197
197
198 user_id = getattr(user, 'user_id', None)
198 user_id = getattr(user, 'user_id', None)
199 if not user_id:
199 if not user_id:
200 # maybe we have username ? Try to figure user_id from username
200 # maybe we have username ? Try to figure user_id from username
201 if username:
201 if username:
202 user_id = getattr(
202 user_id = getattr(
203 User.get_by_username(username), 'user_id', None)
203 User.get_by_username(username), 'user_id', None)
204
204
205 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
205 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
206 if not ip_addr:
206 if not ip_addr:
207 pass
207 pass
208
208
209 if not user_data:
209 if not user_data:
210 # try to get this from the auth user
210 # try to get this from the auth user
211 if isinstance(user, AuthUser):
211 if isinstance(user, AuthUser):
212 user_data = {
212 user_data = {
213 'username': user.username,
213 'username': user.username,
214 'email': user.email,
214 'email': user.email,
215 }
215 }
216
216
217 repository_name = getattr(repo, 'repo_name', None)
217 repository_name = getattr(repo, 'repo_name', None)
218 repository_id = getattr(repo, 'repo_id', None)
218 repository_id = getattr(repo, 'repo_id', None)
219 if not repository_id:
219 if not repository_id:
220 # maybe we have repo_name ? Try to figure repo_id from repo_name
220 # maybe we have repo_name ? Try to figure repo_id from repo_name
221 if repository_name:
221 if repository_name:
222 repository_id = getattr(
222 repository_id = getattr(
223 Repository.get_by_repo_name(repository_name), 'repo_id', None)
223 Repository.get_by_repo_name(repository_name), 'repo_id', None)
224
224
225 user_log = _store_log(
225 user_log = _store_log(
226 action_name=safe_unicode(action),
226 action_name=safe_unicode(action),
227 action_data=action_data or {},
227 action_data=action_data or {},
228 user_id=user_id,
228 user_id=user_id,
229 username=username,
229 username=username,
230 user_data=user_data or {},
230 user_data=user_data or {},
231 ip_address=safe_unicode(ip_addr),
231 ip_address=safe_unicode(ip_addr),
232 repository_id=repository_id,
232 repository_id=repository_id,
233 repository_name=repository_name
233 repository_name=repository_name
234 )
234 )
235 sa_session.add(user_log)
235 sa_session.add(user_log)
236 if commit:
236 if commit:
237 sa_session.commit()
237 sa_session.commit()
238
238
239 except Exception:
239 except Exception:
240 log.exception('AUDIT: failed to store audit log')
240 log.exception('AUDIT: failed to store audit log')
@@ -1,299 +1,299 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode task modules, containing all task that suppose to be run
22 RhodeCode task modules, containing all task that suppose to be run
23 by celery daemon
23 by celery daemon
24 """
24 """
25
25
26
26
27 import os
27 import os
28 import logging
28 import logging
29
29
30 from celery.task import task
30 from celery.task import task
31 from pylons import config
31 from pylons import config
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.celerylib import (
35 from rhodecode.lib.celerylib import (
36 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
37 get_session, vcsconnection, RhodecodeCeleryTask)
37 get_session, vcsconnection, RhodecodeCeleryTask)
38 from rhodecode.lib.hooks_base import log_create_repository
38 from rhodecode.lib.hooks_base import log_create_repository
39 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
40 from rhodecode.lib.utils import add_cache
40 from rhodecode.lib.utils import add_cache
41 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.lib.utils2 import safe_int, str2bool
42 from rhodecode.model.db import Repository, User
42 from rhodecode.model.db import Repository, User
43
43
44
44
45 add_cache(config) # pragma: no cover
45 add_cache(config) # pragma: no cover
46
46
47
47
48 def get_logger(cls):
48 def get_logger(cls):
49 if rhodecode.CELERY_ENABLED:
49 if rhodecode.CELERY_ENABLED:
50 try:
50 try:
51 log = cls.get_logger()
51 log = cls.get_logger()
52 except Exception:
52 except Exception:
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54 else:
54 else:
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 return log
57 return log
58
58
59
59
60 @task(ignore_result=True, base=RhodecodeCeleryTask)
60 @task(ignore_result=True, base=RhodecodeCeleryTask)
61 @dbsession
61 @dbsession
62 def send_email(recipients, subject, body='', html_body='', email_config=None):
62 def send_email(recipients, subject, body='', html_body='', email_config=None):
63 """
63 """
64 Sends an email with defined parameters from the .ini files.
64 Sends an email with defined parameters from the .ini files.
65
65
66 :param recipients: list of recipients, it this is empty the defined email
66 :param recipients: list of recipients, it this is empty the defined email
67 address from field 'email_to' is used instead
67 address from field 'email_to' is used instead
68 :param subject: subject of the mail
68 :param subject: subject of the mail
69 :param body: body of the mail
69 :param body: body of the mail
70 :param html_body: html version of body
70 :param html_body: html version of body
71 """
71 """
72 log = get_logger(send_email)
72 log = get_logger(send_email)
73
73
74 email_config = email_config or rhodecode.CONFIG
74 email_config = email_config or rhodecode.CONFIG
75 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
75 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
76 if not recipients:
76 if not recipients:
77 # if recipients are not defined we send to email_config + all admins
77 # if recipients are not defined we send to email_config + all admins
78 admins = [
78 admins = [
79 u.email for u in User.query().filter(User.admin == True).all()]
79 u.email for u in User.query().filter(User.admin == True).all()]
80 recipients = [email_config.get('email_to')] + admins
80 recipients = [email_config.get('email_to')] + admins
81
81
82 mail_server = email_config.get('smtp_server') or None
82 mail_server = email_config.get('smtp_server') or None
83 if mail_server is None:
83 if mail_server is None:
84 log.error("SMTP server information missing. Sending email failed. "
84 log.error("SMTP server information missing. Sending email failed. "
85 "Make sure that `smtp_server` variable is configured "
85 "Make sure that `smtp_server` variable is configured "
86 "inside the .ini file")
86 "inside the .ini file")
87 return False
87 return False
88
88
89 mail_from = email_config.get('app_email_from', 'RhodeCode')
89 mail_from = email_config.get('app_email_from', 'RhodeCode')
90 user = email_config.get('smtp_username')
90 user = email_config.get('smtp_username')
91 passwd = email_config.get('smtp_password')
91 passwd = email_config.get('smtp_password')
92 mail_port = email_config.get('smtp_port')
92 mail_port = email_config.get('smtp_port')
93 tls = str2bool(email_config.get('smtp_use_tls'))
93 tls = str2bool(email_config.get('smtp_use_tls'))
94 ssl = str2bool(email_config.get('smtp_use_ssl'))
94 ssl = str2bool(email_config.get('smtp_use_ssl'))
95 debug = str2bool(email_config.get('debug'))
95 debug = str2bool(email_config.get('debug'))
96 smtp_auth = email_config.get('smtp_auth')
96 smtp_auth = email_config.get('smtp_auth')
97
97
98 try:
98 try:
99 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
99 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
100 mail_port, ssl, tls, debug=debug)
100 mail_port, ssl, tls, debug=debug)
101 m.send(recipients, subject, body, html_body)
101 m.send(recipients, subject, body, html_body)
102 except Exception:
102 except Exception:
103 log.exception('Mail sending failed')
103 log.exception('Mail sending failed')
104 return False
104 return False
105 return True
105 return True
106
106
107
107
108 @task(ignore_result=True, base=RhodecodeCeleryTask)
108 @task(ignore_result=True, base=RhodecodeCeleryTask)
109 @dbsession
109 @dbsession
110 @vcsconnection
110 @vcsconnection
111 def create_repo(form_data, cur_user):
111 def create_repo(form_data, cur_user):
112 from rhodecode.model.repo import RepoModel
112 from rhodecode.model.repo import RepoModel
113 from rhodecode.model.user import UserModel
113 from rhodecode.model.user import UserModel
114 from rhodecode.model.settings import SettingsModel
114 from rhodecode.model.settings import SettingsModel
115
115
116 log = get_logger(create_repo)
116 log = get_logger(create_repo)
117 DBS = get_session()
117 DBS = get_session()
118
118
119 cur_user = UserModel(DBS)._get_user(cur_user)
119 cur_user = UserModel(DBS)._get_user(cur_user)
120 owner = cur_user
120 owner = cur_user
121
121
122 repo_name = form_data['repo_name']
122 repo_name = form_data['repo_name']
123 repo_name_full = form_data['repo_name_full']
123 repo_name_full = form_data['repo_name_full']
124 repo_type = form_data['repo_type']
124 repo_type = form_data['repo_type']
125 description = form_data['repo_description']
125 description = form_data['repo_description']
126 private = form_data['repo_private']
126 private = form_data['repo_private']
127 clone_uri = form_data.get('clone_uri')
127 clone_uri = form_data.get('clone_uri')
128 repo_group = safe_int(form_data['repo_group'])
128 repo_group = safe_int(form_data['repo_group'])
129 landing_rev = form_data['repo_landing_rev']
129 landing_rev = form_data['repo_landing_rev']
130 copy_fork_permissions = form_data.get('copy_permissions')
130 copy_fork_permissions = form_data.get('copy_permissions')
131 copy_group_permissions = form_data.get('repo_copy_permissions')
131 copy_group_permissions = form_data.get('repo_copy_permissions')
132 fork_of = form_data.get('fork_parent_id')
132 fork_of = form_data.get('fork_parent_id')
133 state = form_data.get('repo_state', Repository.STATE_PENDING)
133 state = form_data.get('repo_state', Repository.STATE_PENDING)
134
134
135 # repo creation defaults, private and repo_type are filled in form
135 # repo creation defaults, private and repo_type are filled in form
136 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
136 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
137 enable_statistics = form_data.get(
137 enable_statistics = form_data.get(
138 'enable_statistics', defs.get('repo_enable_statistics'))
138 'enable_statistics', defs.get('repo_enable_statistics'))
139 enable_locking = form_data.get(
139 enable_locking = form_data.get(
140 'enable_locking', defs.get('repo_enable_locking'))
140 'enable_locking', defs.get('repo_enable_locking'))
141 enable_downloads = form_data.get(
141 enable_downloads = form_data.get(
142 'enable_downloads', defs.get('repo_enable_downloads'))
142 'enable_downloads', defs.get('repo_enable_downloads'))
143
143
144 try:
144 try:
145 repo = RepoModel(DBS)._create_repo(
145 repo = RepoModel(DBS)._create_repo(
146 repo_name=repo_name_full,
146 repo_name=repo_name_full,
147 repo_type=repo_type,
147 repo_type=repo_type,
148 description=description,
148 description=description,
149 owner=owner,
149 owner=owner,
150 private=private,
150 private=private,
151 clone_uri=clone_uri,
151 clone_uri=clone_uri,
152 repo_group=repo_group,
152 repo_group=repo_group,
153 landing_rev=landing_rev,
153 landing_rev=landing_rev,
154 fork_of=fork_of,
154 fork_of=fork_of,
155 copy_fork_permissions=copy_fork_permissions,
155 copy_fork_permissions=copy_fork_permissions,
156 copy_group_permissions=copy_group_permissions,
156 copy_group_permissions=copy_group_permissions,
157 enable_statistics=enable_statistics,
157 enable_statistics=enable_statistics,
158 enable_locking=enable_locking,
158 enable_locking=enable_locking,
159 enable_downloads=enable_downloads,
159 enable_downloads=enable_downloads,
160 state=state
160 state=state
161 )
161 )
162 DBS.commit()
162 DBS.commit()
163
163
164 # now create this repo on Filesystem
164 # now create this repo on Filesystem
165 RepoModel(DBS)._create_filesystem_repo(
165 RepoModel(DBS)._create_filesystem_repo(
166 repo_name=repo_name,
166 repo_name=repo_name,
167 repo_type=repo_type,
167 repo_type=repo_type,
168 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
168 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
169 clone_uri=clone_uri,
169 clone_uri=clone_uri,
170 )
170 )
171 repo = Repository.get_by_repo_name(repo_name_full)
171 repo = Repository.get_by_repo_name(repo_name_full)
172 log_create_repository(created_by=owner.username, **repo.get_dict())
172 log_create_repository(created_by=owner.username, **repo.get_dict())
173
173
174 # update repo commit caches initially
174 # update repo commit caches initially
175 repo.update_commit_cache()
175 repo.update_commit_cache()
176
176
177 # set new created state
177 # set new created state
178 repo.set_state(Repository.STATE_CREATED)
178 repo.set_state(Repository.STATE_CREATED)
179 repo_id = repo.repo_id
179 repo_id = repo.repo_id
180 repo_data = repo.get_api_data()
180 repo_data = repo.get_api_data()
181
181
182 audit_logger.store(
182 audit_logger.store_web(
183 action='repo.create',
183 action='repo.create',
184 action_data={'data': repo_data},
184 action_data={'data': repo_data},
185 user=cur_user,
185 user=cur_user,
186 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
186 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
187
187
188 DBS.commit()
188 DBS.commit()
189 except Exception:
189 except Exception:
190 log.warning('Exception occurred when creating repository, '
190 log.warning('Exception occurred when creating repository, '
191 'doing cleanup...', exc_info=True)
191 'doing cleanup...', exc_info=True)
192 # rollback things manually !
192 # rollback things manually !
193 repo = Repository.get_by_repo_name(repo_name_full)
193 repo = Repository.get_by_repo_name(repo_name_full)
194 if repo:
194 if repo:
195 Repository.delete(repo.repo_id)
195 Repository.delete(repo.repo_id)
196 DBS.commit()
196 DBS.commit()
197 RepoModel(DBS)._delete_filesystem_repo(repo)
197 RepoModel(DBS)._delete_filesystem_repo(repo)
198 raise
198 raise
199
199
200 # it's an odd fix to make celery fail task when exception occurs
200 # it's an odd fix to make celery fail task when exception occurs
201 def on_failure(self, *args, **kwargs):
201 def on_failure(self, *args, **kwargs):
202 pass
202 pass
203
203
204 return True
204 return True
205
205
206
206
207 @task(ignore_result=True, base=RhodecodeCeleryTask)
207 @task(ignore_result=True, base=RhodecodeCeleryTask)
208 @dbsession
208 @dbsession
209 @vcsconnection
209 @vcsconnection
210 def create_repo_fork(form_data, cur_user):
210 def create_repo_fork(form_data, cur_user):
211 """
211 """
212 Creates a fork of repository using internal VCS methods
212 Creates a fork of repository using internal VCS methods
213
213
214 :param form_data:
214 :param form_data:
215 :param cur_user:
215 :param cur_user:
216 """
216 """
217 from rhodecode.model.repo import RepoModel
217 from rhodecode.model.repo import RepoModel
218 from rhodecode.model.user import UserModel
218 from rhodecode.model.user import UserModel
219
219
220 log = get_logger(create_repo_fork)
220 log = get_logger(create_repo_fork)
221 DBS = get_session()
221 DBS = get_session()
222
222
223 cur_user = UserModel(DBS)._get_user(cur_user)
223 cur_user = UserModel(DBS)._get_user(cur_user)
224 owner = cur_user
224 owner = cur_user
225
225
226 repo_name = form_data['repo_name'] # fork in this case
226 repo_name = form_data['repo_name'] # fork in this case
227 repo_name_full = form_data['repo_name_full']
227 repo_name_full = form_data['repo_name_full']
228 repo_type = form_data['repo_type']
228 repo_type = form_data['repo_type']
229 description = form_data['description']
229 description = form_data['description']
230 private = form_data['private']
230 private = form_data['private']
231 clone_uri = form_data.get('clone_uri')
231 clone_uri = form_data.get('clone_uri')
232 repo_group = safe_int(form_data['repo_group'])
232 repo_group = safe_int(form_data['repo_group'])
233 landing_rev = form_data['landing_rev']
233 landing_rev = form_data['landing_rev']
234 copy_fork_permissions = form_data.get('copy_permissions')
234 copy_fork_permissions = form_data.get('copy_permissions')
235 fork_id = safe_int(form_data.get('fork_parent_id'))
235 fork_id = safe_int(form_data.get('fork_parent_id'))
236
236
237 try:
237 try:
238 fork_of = RepoModel(DBS)._get_repo(fork_id)
238 fork_of = RepoModel(DBS)._get_repo(fork_id)
239 RepoModel(DBS)._create_repo(
239 RepoModel(DBS)._create_repo(
240 repo_name=repo_name_full,
240 repo_name=repo_name_full,
241 repo_type=repo_type,
241 repo_type=repo_type,
242 description=description,
242 description=description,
243 owner=owner,
243 owner=owner,
244 private=private,
244 private=private,
245 clone_uri=clone_uri,
245 clone_uri=clone_uri,
246 repo_group=repo_group,
246 repo_group=repo_group,
247 landing_rev=landing_rev,
247 landing_rev=landing_rev,
248 fork_of=fork_of,
248 fork_of=fork_of,
249 copy_fork_permissions=copy_fork_permissions
249 copy_fork_permissions=copy_fork_permissions
250 )
250 )
251
251
252 DBS.commit()
252 DBS.commit()
253
253
254 base_path = Repository.base_path()
254 base_path = Repository.base_path()
255 source_repo_path = os.path.join(base_path, fork_of.repo_name)
255 source_repo_path = os.path.join(base_path, fork_of.repo_name)
256
256
257 # now create this repo on Filesystem
257 # now create this repo on Filesystem
258 RepoModel(DBS)._create_filesystem_repo(
258 RepoModel(DBS)._create_filesystem_repo(
259 repo_name=repo_name,
259 repo_name=repo_name,
260 repo_type=repo_type,
260 repo_type=repo_type,
261 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
261 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
262 clone_uri=source_repo_path,
262 clone_uri=source_repo_path,
263 )
263 )
264 repo = Repository.get_by_repo_name(repo_name_full)
264 repo = Repository.get_by_repo_name(repo_name_full)
265 log_create_repository(created_by=owner.username, **repo.get_dict())
265 log_create_repository(created_by=owner.username, **repo.get_dict())
266
266
267 # update repo commit caches initially
267 # update repo commit caches initially
268 config = repo._config
268 config = repo._config
269 config.set('extensions', 'largefiles', '')
269 config.set('extensions', 'largefiles', '')
270 repo.update_commit_cache(config=config)
270 repo.update_commit_cache(config=config)
271
271
272 # set new created state
272 # set new created state
273 repo.set_state(Repository.STATE_CREATED)
273 repo.set_state(Repository.STATE_CREATED)
274
274
275 repo_id = repo.repo_id
275 repo_id = repo.repo_id
276 repo_data = repo.get_api_data()
276 repo_data = repo.get_api_data()
277 audit_logger.store(
277 audit_logger.store_web(
278 action='repo.fork',
278 action='repo.fork',
279 action_data={'data': repo_data},
279 action_data={'data': repo_data},
280 user=cur_user,
280 user=cur_user,
281 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
281 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
282
282
283 DBS.commit()
283 DBS.commit()
284 except Exception as e:
284 except Exception as e:
285 log.warning('Exception %s occurred when forking repository, '
285 log.warning('Exception %s occurred when forking repository, '
286 'doing cleanup...', e)
286 'doing cleanup...', e)
287 # rollback things manually !
287 # rollback things manually !
288 repo = Repository.get_by_repo_name(repo_name_full)
288 repo = Repository.get_by_repo_name(repo_name_full)
289 if repo:
289 if repo:
290 Repository.delete(repo.repo_id)
290 Repository.delete(repo.repo_id)
291 DBS.commit()
291 DBS.commit()
292 RepoModel(DBS)._delete_filesystem_repo(repo)
292 RepoModel(DBS)._delete_filesystem_repo(repo)
293 raise
293 raise
294
294
295 # it's an odd fix to make celery fail task when exception occurs
295 # it's an odd fix to make celery fail task when exception occurs
296 def on_failure(self, *args, **kwargs):
296 def on_failure(self, *args, **kwargs):
297 pass
297 pass
298
298
299 return True
299 return True
General Comments 0
You need to be logged in to leave comments. Login now