##// END OF EJS Templates
api: Add an entry for pr shadow repositories to api functions.
Martin Bornhold -
r893:b7927ff7 default
parent child Browse files
Show More
@@ -1,660 +1,666 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 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 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError
24 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 has_repo_permissions, resolve_ref_or_error)
28 has_repo_permissions, resolve_ref_or_error)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.base import vcs_operation_context
30 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.model.changeset_status import ChangesetStatusModel
32 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.comment import ChangesetCommentsModel
33 from rhodecode.model.comment import ChangesetCommentsModel
34 from rhodecode.model.db import Session, ChangesetStatus
34 from rhodecode.model.db import Session, ChangesetStatus
35 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.pull_request import PullRequestModel
36 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 @jsonrpc_method()
41 @jsonrpc_method()
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
43 """
43 """
44 Get a pull request based on the given ID.
44 Get a pull request based on the given ID.
45
45
46 :param apiuser: This is filled automatically from the |authtoken|.
46 :param apiuser: This is filled automatically from the |authtoken|.
47 :type apiuser: AuthUser
47 :type apiuser: AuthUser
48 :param repoid: Repository name or repository ID from where the pull
48 :param repoid: Repository name or repository ID from where the pull
49 request was opened.
49 request was opened.
50 :type repoid: str or int
50 :type repoid: str or int
51 :param pullrequestid: ID of the requested pull request.
51 :param pullrequestid: ID of the requested pull request.
52 :type pullrequestid: int
52 :type pullrequestid: int
53
53
54 Example output:
54 Example output:
55
55
56 .. code-block:: bash
56 .. code-block:: bash
57
57
58 "id": <id_given_in_input>,
58 "id": <id_given_in_input>,
59 "result":
59 "result":
60 {
60 {
61 "pull_request_id": "<pull_request_id>",
61 "pull_request_id": "<pull_request_id>",
62 "url": "<url>",
62 "url": "<url>",
63 "title": "<title>",
63 "title": "<title>",
64 "description": "<description>",
64 "description": "<description>",
65 "status" : "<status>",
65 "status" : "<status>",
66 "created_on": "<date_time_created>",
66 "created_on": "<date_time_created>",
67 "updated_on": "<date_time_updated>",
67 "updated_on": "<date_time_updated>",
68 "commit_ids": [
68 "commit_ids": [
69 ...
69 ...
70 "<commit_id>",
70 "<commit_id>",
71 "<commit_id>",
71 "<commit_id>",
72 ...
72 ...
73 ],
73 ],
74 "review_status": "<review_status>",
74 "review_status": "<review_status>",
75 "mergeable": {
75 "mergeable": {
76 "status": "<bool>",
76 "status": "<bool>",
77 "message": "<message>",
77 "message": "<message>",
78 },
78 },
79 "source": {
79 "source": {
80 "clone_url": "<clone_url>",
80 "clone_url": "<clone_url>",
81 "repository": "<repository_name>",
81 "repository": "<repository_name>",
82 "reference":
82 "reference":
83 {
83 {
84 "name": "<name>",
84 "name": "<name>",
85 "type": "<type>",
85 "type": "<type>",
86 "commit_id": "<commit_id>",
86 "commit_id": "<commit_id>",
87 }
87 }
88 },
88 },
89 "target": {
89 "target": {
90 "clone_url": "<clone_url>",
90 "clone_url": "<clone_url>",
91 "repository": "<repository_name>",
91 "repository": "<repository_name>",
92 "reference":
92 "reference":
93 {
93 {
94 "name": "<name>",
94 "name": "<name>",
95 "type": "<type>",
95 "type": "<type>",
96 "commit_id": "<commit_id>",
96 "commit_id": "<commit_id>",
97 }
97 }
98 },
98 },
99 "shadow": {
100 "clone_url": "<clone_url>",
101 },
99 "author": <user_obj>,
102 "author": <user_obj>,
100 "reviewers": [
103 "reviewers": [
101 ...
104 ...
102 {
105 {
103 "user": "<user_obj>",
106 "user": "<user_obj>",
104 "review_status": "<review_status>",
107 "review_status": "<review_status>",
105 }
108 }
106 ...
109 ...
107 ]
110 ]
108 },
111 },
109 "error": null
112 "error": null
110 """
113 """
111 get_repo_or_error(repoid)
114 get_repo_or_error(repoid)
112 pull_request = get_pull_request_or_error(pullrequestid)
115 pull_request = get_pull_request_or_error(pullrequestid)
113 if not PullRequestModel().check_user_read(
116 if not PullRequestModel().check_user_read(
114 pull_request, apiuser, api=True):
117 pull_request, apiuser, api=True):
115 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
118 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
116 data = pull_request.get_api_data()
119 data = pull_request.get_api_data()
117 return data
120 return data
118
121
119
122
120 @jsonrpc_method()
123 @jsonrpc_method()
121 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
124 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
122 """
125 """
123 Get all pull requests from the repository specified in `repoid`.
126 Get all pull requests from the repository specified in `repoid`.
124
127
125 :param apiuser: This is filled automatically from the |authtoken|.
128 :param apiuser: This is filled automatically from the |authtoken|.
126 :type apiuser: AuthUser
129 :type apiuser: AuthUser
127 :param repoid: Repository name or repository ID.
130 :param repoid: Repository name or repository ID.
128 :type repoid: str or int
131 :type repoid: str or int
129 :param status: Only return pull requests with the specified status.
132 :param status: Only return pull requests with the specified status.
130 Valid options are.
133 Valid options are.
131 * ``new`` (default)
134 * ``new`` (default)
132 * ``open``
135 * ``open``
133 * ``closed``
136 * ``closed``
134 :type status: str
137 :type status: str
135
138
136 Example output:
139 Example output:
137
140
138 .. code-block:: bash
141 .. code-block:: bash
139
142
140 "id": <id_given_in_input>,
143 "id": <id_given_in_input>,
141 "result":
144 "result":
142 [
145 [
143 ...
146 ...
144 {
147 {
145 "pull_request_id": "<pull_request_id>",
148 "pull_request_id": "<pull_request_id>",
146 "url": "<url>",
149 "url": "<url>",
147 "title" : "<title>",
150 "title" : "<title>",
148 "description": "<description>",
151 "description": "<description>",
149 "status": "<status>",
152 "status": "<status>",
150 "created_on": "<date_time_created>",
153 "created_on": "<date_time_created>",
151 "updated_on": "<date_time_updated>",
154 "updated_on": "<date_time_updated>",
152 "commit_ids": [
155 "commit_ids": [
153 ...
156 ...
154 "<commit_id>",
157 "<commit_id>",
155 "<commit_id>",
158 "<commit_id>",
156 ...
159 ...
157 ],
160 ],
158 "review_status": "<review_status>",
161 "review_status": "<review_status>",
159 "mergeable": {
162 "mergeable": {
160 "status": "<bool>",
163 "status": "<bool>",
161 "message: "<message>",
164 "message: "<message>",
162 },
165 },
163 "source": {
166 "source": {
164 "clone_url": "<clone_url>",
167 "clone_url": "<clone_url>",
165 "reference":
168 "reference":
166 {
169 {
167 "name": "<name>",
170 "name": "<name>",
168 "type": "<type>",
171 "type": "<type>",
169 "commit_id": "<commit_id>",
172 "commit_id": "<commit_id>",
170 }
173 }
171 },
174 },
172 "target": {
175 "target": {
173 "clone_url": "<clone_url>",
176 "clone_url": "<clone_url>",
174 "reference":
177 "reference":
175 {
178 {
176 "name": "<name>",
179 "name": "<name>",
177 "type": "<type>",
180 "type": "<type>",
178 "commit_id": "<commit_id>",
181 "commit_id": "<commit_id>",
179 }
182 }
180 },
183 },
184 "shadow": {
185 "clone_url": "<clone_url>",
186 },
181 "author": <user_obj>,
187 "author": <user_obj>,
182 "reviewers": [
188 "reviewers": [
183 ...
189 ...
184 {
190 {
185 "user": "<user_obj>",
191 "user": "<user_obj>",
186 "review_status": "<review_status>",
192 "review_status": "<review_status>",
187 }
193 }
188 ...
194 ...
189 ]
195 ]
190 }
196 }
191 ...
197 ...
192 ],
198 ],
193 "error": null
199 "error": null
194
200
195 """
201 """
196 repo = get_repo_or_error(repoid)
202 repo = get_repo_or_error(repoid)
197 if not has_superadmin_permission(apiuser):
203 if not has_superadmin_permission(apiuser):
198 _perms = (
204 _perms = (
199 'repository.admin', 'repository.write', 'repository.read',)
205 'repository.admin', 'repository.write', 'repository.read',)
200 has_repo_permissions(apiuser, repoid, repo, _perms)
206 has_repo_permissions(apiuser, repoid, repo, _perms)
201
207
202 status = Optional.extract(status)
208 status = Optional.extract(status)
203 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
209 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
204 data = [pr.get_api_data() for pr in pull_requests]
210 data = [pr.get_api_data() for pr in pull_requests]
205 return data
211 return data
206
212
207
213
208 @jsonrpc_method()
214 @jsonrpc_method()
209 def merge_pull_request(request, apiuser, repoid, pullrequestid,
215 def merge_pull_request(request, apiuser, repoid, pullrequestid,
210 userid=Optional(OAttr('apiuser'))):
216 userid=Optional(OAttr('apiuser'))):
211 """
217 """
212 Merge the pull request specified by `pullrequestid` into its target
218 Merge the pull request specified by `pullrequestid` into its target
213 repository.
219 repository.
214
220
215 :param apiuser: This is filled automatically from the |authtoken|.
221 :param apiuser: This is filled automatically from the |authtoken|.
216 :type apiuser: AuthUser
222 :type apiuser: AuthUser
217 :param repoid: The Repository name or repository ID of the
223 :param repoid: The Repository name or repository ID of the
218 target repository to which the |pr| is to be merged.
224 target repository to which the |pr| is to be merged.
219 :type repoid: str or int
225 :type repoid: str or int
220 :param pullrequestid: ID of the pull request which shall be merged.
226 :param pullrequestid: ID of the pull request which shall be merged.
221 :type pullrequestid: int
227 :type pullrequestid: int
222 :param userid: Merge the pull request as this user.
228 :param userid: Merge the pull request as this user.
223 :type userid: Optional(str or int)
229 :type userid: Optional(str or int)
224
230
225 Example output:
231 Example output:
226
232
227 .. code-block:: bash
233 .. code-block:: bash
228
234
229 "id": <id_given_in_input>,
235 "id": <id_given_in_input>,
230 "result":
236 "result":
231 {
237 {
232 "executed": "<bool>",
238 "executed": "<bool>",
233 "failure_reason": "<int>",
239 "failure_reason": "<int>",
234 "merge_commit_id": "<merge_commit_id>",
240 "merge_commit_id": "<merge_commit_id>",
235 "possible": "<bool>"
241 "possible": "<bool>"
236 },
242 },
237 "error": null
243 "error": null
238
244
239 """
245 """
240 repo = get_repo_or_error(repoid)
246 repo = get_repo_or_error(repoid)
241 if not isinstance(userid, Optional):
247 if not isinstance(userid, Optional):
242 if (has_superadmin_permission(apiuser) or
248 if (has_superadmin_permission(apiuser) or
243 HasRepoPermissionAnyApi('repository.admin')(
249 HasRepoPermissionAnyApi('repository.admin')(
244 user=apiuser, repo_name=repo.repo_name)):
250 user=apiuser, repo_name=repo.repo_name)):
245 apiuser = get_user_or_error(userid)
251 apiuser = get_user_or_error(userid)
246 else:
252 else:
247 raise JSONRPCError('userid is not the same as your user')
253 raise JSONRPCError('userid is not the same as your user')
248
254
249 pull_request = get_pull_request_or_error(pullrequestid)
255 pull_request = get_pull_request_or_error(pullrequestid)
250 if not PullRequestModel().check_user_merge(
256 if not PullRequestModel().check_user_merge(
251 pull_request, apiuser, api=True):
257 pull_request, apiuser, api=True):
252 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
258 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
253 if pull_request.is_closed():
259 if pull_request.is_closed():
254 raise JSONRPCError(
260 raise JSONRPCError(
255 'pull request `%s` merge failed, pull request is closed' % (
261 'pull request `%s` merge failed, pull request is closed' % (
256 pullrequestid,))
262 pullrequestid,))
257
263
258 target_repo = pull_request.target_repo
264 target_repo = pull_request.target_repo
259 extras = vcs_operation_context(
265 extras = vcs_operation_context(
260 request.environ, repo_name=target_repo.repo_name,
266 request.environ, repo_name=target_repo.repo_name,
261 username=apiuser.username, action='push',
267 username=apiuser.username, action='push',
262 scm=target_repo.repo_type)
268 scm=target_repo.repo_type)
263 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
269 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
264 if data.executed:
270 if data.executed:
265 PullRequestModel().close_pull_request(
271 PullRequestModel().close_pull_request(
266 pull_request.pull_request_id, apiuser)
272 pull_request.pull_request_id, apiuser)
267
273
268 Session().commit()
274 Session().commit()
269 return data
275 return data
270
276
271
277
272 @jsonrpc_method()
278 @jsonrpc_method()
273 def close_pull_request(request, apiuser, repoid, pullrequestid,
279 def close_pull_request(request, apiuser, repoid, pullrequestid,
274 userid=Optional(OAttr('apiuser'))):
280 userid=Optional(OAttr('apiuser'))):
275 """
281 """
276 Close the pull request specified by `pullrequestid`.
282 Close the pull request specified by `pullrequestid`.
277
283
278 :param apiuser: This is filled automatically from the |authtoken|.
284 :param apiuser: This is filled automatically from the |authtoken|.
279 :type apiuser: AuthUser
285 :type apiuser: AuthUser
280 :param repoid: Repository name or repository ID to which the pull
286 :param repoid: Repository name or repository ID to which the pull
281 request belongs.
287 request belongs.
282 :type repoid: str or int
288 :type repoid: str or int
283 :param pullrequestid: ID of the pull request to be closed.
289 :param pullrequestid: ID of the pull request to be closed.
284 :type pullrequestid: int
290 :type pullrequestid: int
285 :param userid: Close the pull request as this user.
291 :param userid: Close the pull request as this user.
286 :type userid: Optional(str or int)
292 :type userid: Optional(str or int)
287
293
288 Example output:
294 Example output:
289
295
290 .. code-block:: bash
296 .. code-block:: bash
291
297
292 "id": <id_given_in_input>,
298 "id": <id_given_in_input>,
293 "result":
299 "result":
294 {
300 {
295 "pull_request_id": "<int>",
301 "pull_request_id": "<int>",
296 "closed": "<bool>"
302 "closed": "<bool>"
297 },
303 },
298 "error": null
304 "error": null
299
305
300 """
306 """
301 repo = get_repo_or_error(repoid)
307 repo = get_repo_or_error(repoid)
302 if not isinstance(userid, Optional):
308 if not isinstance(userid, Optional):
303 if (has_superadmin_permission(apiuser) or
309 if (has_superadmin_permission(apiuser) or
304 HasRepoPermissionAnyApi('repository.admin')(
310 HasRepoPermissionAnyApi('repository.admin')(
305 user=apiuser, repo_name=repo.repo_name)):
311 user=apiuser, repo_name=repo.repo_name)):
306 apiuser = get_user_or_error(userid)
312 apiuser = get_user_or_error(userid)
307 else:
313 else:
308 raise JSONRPCError('userid is not the same as your user')
314 raise JSONRPCError('userid is not the same as your user')
309
315
310 pull_request = get_pull_request_or_error(pullrequestid)
316 pull_request = get_pull_request_or_error(pullrequestid)
311 if not PullRequestModel().check_user_update(
317 if not PullRequestModel().check_user_update(
312 pull_request, apiuser, api=True):
318 pull_request, apiuser, api=True):
313 raise JSONRPCError(
319 raise JSONRPCError(
314 'pull request `%s` close failed, no permission to close.' % (
320 'pull request `%s` close failed, no permission to close.' % (
315 pullrequestid,))
321 pullrequestid,))
316 if pull_request.is_closed():
322 if pull_request.is_closed():
317 raise JSONRPCError(
323 raise JSONRPCError(
318 'pull request `%s` is already closed' % (pullrequestid,))
324 'pull request `%s` is already closed' % (pullrequestid,))
319
325
320 PullRequestModel().close_pull_request(
326 PullRequestModel().close_pull_request(
321 pull_request.pull_request_id, apiuser)
327 pull_request.pull_request_id, apiuser)
322 Session().commit()
328 Session().commit()
323 data = {
329 data = {
324 'pull_request_id': pull_request.pull_request_id,
330 'pull_request_id': pull_request.pull_request_id,
325 'closed': True,
331 'closed': True,
326 }
332 }
327 return data
333 return data
328
334
329
335
330 @jsonrpc_method()
336 @jsonrpc_method()
331 def comment_pull_request(request, apiuser, repoid, pullrequestid,
337 def comment_pull_request(request, apiuser, repoid, pullrequestid,
332 message=Optional(None), status=Optional(None),
338 message=Optional(None), status=Optional(None),
333 userid=Optional(OAttr('apiuser'))):
339 userid=Optional(OAttr('apiuser'))):
334 """
340 """
335 Comment on the pull request specified with the `pullrequestid`,
341 Comment on the pull request specified with the `pullrequestid`,
336 in the |repo| specified by the `repoid`, and optionally change the
342 in the |repo| specified by the `repoid`, and optionally change the
337 review status.
343 review status.
338
344
339 :param apiuser: This is filled automatically from the |authtoken|.
345 :param apiuser: This is filled automatically from the |authtoken|.
340 :type apiuser: AuthUser
346 :type apiuser: AuthUser
341 :param repoid: The repository name or repository ID.
347 :param repoid: The repository name or repository ID.
342 :type repoid: str or int
348 :type repoid: str or int
343 :param pullrequestid: The pull request ID.
349 :param pullrequestid: The pull request ID.
344 :type pullrequestid: int
350 :type pullrequestid: int
345 :param message: The text content of the comment.
351 :param message: The text content of the comment.
346 :type message: str
352 :type message: str
347 :param status: (**Optional**) Set the approval status of the pull
353 :param status: (**Optional**) Set the approval status of the pull
348 request. Valid options are:
354 request. Valid options are:
349 * not_reviewed
355 * not_reviewed
350 * approved
356 * approved
351 * rejected
357 * rejected
352 * under_review
358 * under_review
353 :type status: str
359 :type status: str
354 :param userid: Comment on the pull request as this user
360 :param userid: Comment on the pull request as this user
355 :type userid: Optional(str or int)
361 :type userid: Optional(str or int)
356
362
357 Example output:
363 Example output:
358
364
359 .. code-block:: bash
365 .. code-block:: bash
360
366
361 id : <id_given_in_input>
367 id : <id_given_in_input>
362 result :
368 result :
363 {
369 {
364 "pull_request_id": "<Integer>",
370 "pull_request_id": "<Integer>",
365 "comment_id": "<Integer>"
371 "comment_id": "<Integer>"
366 }
372 }
367 error : null
373 error : null
368 """
374 """
369 repo = get_repo_or_error(repoid)
375 repo = get_repo_or_error(repoid)
370 if not isinstance(userid, Optional):
376 if not isinstance(userid, Optional):
371 if (has_superadmin_permission(apiuser) or
377 if (has_superadmin_permission(apiuser) or
372 HasRepoPermissionAnyApi('repository.admin')(
378 HasRepoPermissionAnyApi('repository.admin')(
373 user=apiuser, repo_name=repo.repo_name)):
379 user=apiuser, repo_name=repo.repo_name)):
374 apiuser = get_user_or_error(userid)
380 apiuser = get_user_or_error(userid)
375 else:
381 else:
376 raise JSONRPCError('userid is not the same as your user')
382 raise JSONRPCError('userid is not the same as your user')
377
383
378 pull_request = get_pull_request_or_error(pullrequestid)
384 pull_request = get_pull_request_or_error(pullrequestid)
379 if not PullRequestModel().check_user_read(
385 if not PullRequestModel().check_user_read(
380 pull_request, apiuser, api=True):
386 pull_request, apiuser, api=True):
381 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
387 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
382 message = Optional.extract(message)
388 message = Optional.extract(message)
383 status = Optional.extract(status)
389 status = Optional.extract(status)
384 if not message and not status:
390 if not message and not status:
385 raise JSONRPCError('message and status parameter missing')
391 raise JSONRPCError('message and status parameter missing')
386
392
387 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
393 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
388 status is not None):
394 status is not None):
389 raise JSONRPCError('unknown comment status`%s`' % status)
395 raise JSONRPCError('unknown comment status`%s`' % status)
390
396
391 allowed_to_change_status = PullRequestModel().check_user_change_status(
397 allowed_to_change_status = PullRequestModel().check_user_change_status(
392 pull_request, apiuser)
398 pull_request, apiuser)
393 text = message
399 text = message
394 if status and allowed_to_change_status:
400 if status and allowed_to_change_status:
395 st_message = (('Status change %(transition_icon)s %(status)s')
401 st_message = (('Status change %(transition_icon)s %(status)s')
396 % {'transition_icon': '>',
402 % {'transition_icon': '>',
397 'status': ChangesetStatus.get_status_lbl(status)})
403 'status': ChangesetStatus.get_status_lbl(status)})
398 text = message or st_message
404 text = message or st_message
399
405
400 rc_config = SettingsModel().get_all_settings()
406 rc_config = SettingsModel().get_all_settings()
401 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
407 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
402 comment = ChangesetCommentsModel().create(
408 comment = ChangesetCommentsModel().create(
403 text=text,
409 text=text,
404 repo=pull_request.target_repo.repo_id,
410 repo=pull_request.target_repo.repo_id,
405 user=apiuser.user_id,
411 user=apiuser.user_id,
406 pull_request=pull_request.pull_request_id,
412 pull_request=pull_request.pull_request_id,
407 f_path=None,
413 f_path=None,
408 line_no=None,
414 line_no=None,
409 status_change=(ChangesetStatus.get_status_lbl(status)
415 status_change=(ChangesetStatus.get_status_lbl(status)
410 if status and allowed_to_change_status else None),
416 if status and allowed_to_change_status else None),
411 status_change_type=(status
417 status_change_type=(status
412 if status and allowed_to_change_status else None),
418 if status and allowed_to_change_status else None),
413 closing_pr=False,
419 closing_pr=False,
414 renderer=renderer
420 renderer=renderer
415 )
421 )
416
422
417 if allowed_to_change_status and status:
423 if allowed_to_change_status and status:
418 ChangesetStatusModel().set_status(
424 ChangesetStatusModel().set_status(
419 pull_request.target_repo.repo_id,
425 pull_request.target_repo.repo_id,
420 status,
426 status,
421 apiuser.user_id,
427 apiuser.user_id,
422 comment,
428 comment,
423 pull_request=pull_request.pull_request_id
429 pull_request=pull_request.pull_request_id
424 )
430 )
425 Session().flush()
431 Session().flush()
426
432
427 Session().commit()
433 Session().commit()
428 data = {
434 data = {
429 'pull_request_id': pull_request.pull_request_id,
435 'pull_request_id': pull_request.pull_request_id,
430 'comment_id': comment.comment_id,
436 'comment_id': comment.comment_id,
431 'status': status
437 'status': status
432 }
438 }
433 return data
439 return data
434
440
435
441
436 @jsonrpc_method()
442 @jsonrpc_method()
437 def create_pull_request(
443 def create_pull_request(
438 request, apiuser, source_repo, target_repo, source_ref, target_ref,
444 request, apiuser, source_repo, target_repo, source_ref, target_ref,
439 title, description=Optional(''), reviewers=Optional(None)):
445 title, description=Optional(''), reviewers=Optional(None)):
440 """
446 """
441 Creates a new pull request.
447 Creates a new pull request.
442
448
443 Accepts refs in the following formats:
449 Accepts refs in the following formats:
444
450
445 * branch:<branch_name>:<sha>
451 * branch:<branch_name>:<sha>
446 * branch:<branch_name>
452 * branch:<branch_name>
447 * bookmark:<bookmark_name>:<sha> (Mercurial only)
453 * bookmark:<bookmark_name>:<sha> (Mercurial only)
448 * bookmark:<bookmark_name> (Mercurial only)
454 * bookmark:<bookmark_name> (Mercurial only)
449
455
450 :param apiuser: This is filled automatically from the |authtoken|.
456 :param apiuser: This is filled automatically from the |authtoken|.
451 :type apiuser: AuthUser
457 :type apiuser: AuthUser
452 :param source_repo: Set the source repository name.
458 :param source_repo: Set the source repository name.
453 :type source_repo: str
459 :type source_repo: str
454 :param target_repo: Set the target repository name.
460 :param target_repo: Set the target repository name.
455 :type target_repo: str
461 :type target_repo: str
456 :param source_ref: Set the source ref name.
462 :param source_ref: Set the source ref name.
457 :type source_ref: str
463 :type source_ref: str
458 :param target_ref: Set the target ref name.
464 :param target_ref: Set the target ref name.
459 :type target_ref: str
465 :type target_ref: str
460 :param title: Set the pull request title.
466 :param title: Set the pull request title.
461 :type title: str
467 :type title: str
462 :param description: Set the pull request description.
468 :param description: Set the pull request description.
463 :type description: Optional(str)
469 :type description: Optional(str)
464 :param reviewers: Set the new pull request reviewers list.
470 :param reviewers: Set the new pull request reviewers list.
465 :type reviewers: Optional(list)
471 :type reviewers: Optional(list)
466 Accepts username strings or objects of the format:
472 Accepts username strings or objects of the format:
467 {
473 {
468 'username': 'nick', 'reasons': ['original author']
474 'username': 'nick', 'reasons': ['original author']
469 }
475 }
470 """
476 """
471
477
472 source = get_repo_or_error(source_repo)
478 source = get_repo_or_error(source_repo)
473 target = get_repo_or_error(target_repo)
479 target = get_repo_or_error(target_repo)
474 if not has_superadmin_permission(apiuser):
480 if not has_superadmin_permission(apiuser):
475 _perms = ('repository.admin', 'repository.write', 'repository.read',)
481 _perms = ('repository.admin', 'repository.write', 'repository.read',)
476 has_repo_permissions(apiuser, source_repo, source, _perms)
482 has_repo_permissions(apiuser, source_repo, source, _perms)
477
483
478 full_source_ref = resolve_ref_or_error(source_ref, source)
484 full_source_ref = resolve_ref_or_error(source_ref, source)
479 full_target_ref = resolve_ref_or_error(target_ref, target)
485 full_target_ref = resolve_ref_or_error(target_ref, target)
480 source_commit = get_commit_or_error(full_source_ref, source)
486 source_commit = get_commit_or_error(full_source_ref, source)
481 target_commit = get_commit_or_error(full_target_ref, target)
487 target_commit = get_commit_or_error(full_target_ref, target)
482 source_scm = source.scm_instance()
488 source_scm = source.scm_instance()
483 target_scm = target.scm_instance()
489 target_scm = target.scm_instance()
484
490
485 commit_ranges = target_scm.compare(
491 commit_ranges = target_scm.compare(
486 target_commit.raw_id, source_commit.raw_id, source_scm,
492 target_commit.raw_id, source_commit.raw_id, source_scm,
487 merge=True, pre_load=[])
493 merge=True, pre_load=[])
488
494
489 ancestor = target_scm.get_common_ancestor(
495 ancestor = target_scm.get_common_ancestor(
490 target_commit.raw_id, source_commit.raw_id, source_scm)
496 target_commit.raw_id, source_commit.raw_id, source_scm)
491
497
492 if not commit_ranges:
498 if not commit_ranges:
493 raise JSONRPCError('no commits found')
499 raise JSONRPCError('no commits found')
494
500
495 if not ancestor:
501 if not ancestor:
496 raise JSONRPCError('no common ancestor found')
502 raise JSONRPCError('no common ancestor found')
497
503
498 reviewer_objects = Optional.extract(reviewers) or []
504 reviewer_objects = Optional.extract(reviewers) or []
499 if not isinstance(reviewer_objects, list):
505 if not isinstance(reviewer_objects, list):
500 raise JSONRPCError('reviewers should be specified as a list')
506 raise JSONRPCError('reviewers should be specified as a list')
501
507
502 reviewers_reasons = []
508 reviewers_reasons = []
503 for reviewer_object in reviewer_objects:
509 for reviewer_object in reviewer_objects:
504 reviewer_reasons = []
510 reviewer_reasons = []
505 if isinstance(reviewer_object, (basestring, int)):
511 if isinstance(reviewer_object, (basestring, int)):
506 reviewer_username = reviewer_object
512 reviewer_username = reviewer_object
507 else:
513 else:
508 reviewer_username = reviewer_object['username']
514 reviewer_username = reviewer_object['username']
509 reviewer_reasons = reviewer_object.get('reasons', [])
515 reviewer_reasons = reviewer_object.get('reasons', [])
510
516
511 user = get_user_or_error(reviewer_username)
517 user = get_user_or_error(reviewer_username)
512 reviewers_reasons.append((user.user_id, reviewer_reasons))
518 reviewers_reasons.append((user.user_id, reviewer_reasons))
513
519
514 pull_request_model = PullRequestModel()
520 pull_request_model = PullRequestModel()
515 pull_request = pull_request_model.create(
521 pull_request = pull_request_model.create(
516 created_by=apiuser.user_id,
522 created_by=apiuser.user_id,
517 source_repo=source_repo,
523 source_repo=source_repo,
518 source_ref=full_source_ref,
524 source_ref=full_source_ref,
519 target_repo=target_repo,
525 target_repo=target_repo,
520 target_ref=full_target_ref,
526 target_ref=full_target_ref,
521 revisions=reversed(
527 revisions=reversed(
522 [commit.raw_id for commit in reversed(commit_ranges)]),
528 [commit.raw_id for commit in reversed(commit_ranges)]),
523 reviewers=reviewers_reasons,
529 reviewers=reviewers_reasons,
524 title=title,
530 title=title,
525 description=Optional.extract(description)
531 description=Optional.extract(description)
526 )
532 )
527
533
528 Session().commit()
534 Session().commit()
529 data = {
535 data = {
530 'msg': 'Created new pull request `{}`'.format(title),
536 'msg': 'Created new pull request `{}`'.format(title),
531 'pull_request_id': pull_request.pull_request_id,
537 'pull_request_id': pull_request.pull_request_id,
532 }
538 }
533 return data
539 return data
534
540
535
541
536 @jsonrpc_method()
542 @jsonrpc_method()
537 def update_pull_request(
543 def update_pull_request(
538 request, apiuser, repoid, pullrequestid, title=Optional(''),
544 request, apiuser, repoid, pullrequestid, title=Optional(''),
539 description=Optional(''), reviewers=Optional(None),
545 description=Optional(''), reviewers=Optional(None),
540 update_commits=Optional(None), close_pull_request=Optional(None)):
546 update_commits=Optional(None), close_pull_request=Optional(None)):
541 """
547 """
542 Updates a pull request.
548 Updates a pull request.
543
549
544 :param apiuser: This is filled automatically from the |authtoken|.
550 :param apiuser: This is filled automatically from the |authtoken|.
545 :type apiuser: AuthUser
551 :type apiuser: AuthUser
546 :param repoid: The repository name or repository ID.
552 :param repoid: The repository name or repository ID.
547 :type repoid: str or int
553 :type repoid: str or int
548 :param pullrequestid: The pull request ID.
554 :param pullrequestid: The pull request ID.
549 :type pullrequestid: int
555 :type pullrequestid: int
550 :param title: Set the pull request title.
556 :param title: Set the pull request title.
551 :type title: str
557 :type title: str
552 :param description: Update pull request description.
558 :param description: Update pull request description.
553 :type description: Optional(str)
559 :type description: Optional(str)
554 :param reviewers: Update pull request reviewers list with new value.
560 :param reviewers: Update pull request reviewers list with new value.
555 :type reviewers: Optional(list)
561 :type reviewers: Optional(list)
556 :param update_commits: Trigger update of commits for this pull request
562 :param update_commits: Trigger update of commits for this pull request
557 :type: update_commits: Optional(bool)
563 :type: update_commits: Optional(bool)
558 :param close_pull_request: Close this pull request with rejected state
564 :param close_pull_request: Close this pull request with rejected state
559 :type: close_pull_request: Optional(bool)
565 :type: close_pull_request: Optional(bool)
560
566
561 Example output:
567 Example output:
562
568
563 .. code-block:: bash
569 .. code-block:: bash
564
570
565 id : <id_given_in_input>
571 id : <id_given_in_input>
566 result :
572 result :
567 {
573 {
568 "msg": "Updated pull request `63`",
574 "msg": "Updated pull request `63`",
569 "pull_request": <pull_request_object>,
575 "pull_request": <pull_request_object>,
570 "updated_reviewers": {
576 "updated_reviewers": {
571 "added": [
577 "added": [
572 "username"
578 "username"
573 ],
579 ],
574 "removed": []
580 "removed": []
575 },
581 },
576 "updated_commits": {
582 "updated_commits": {
577 "added": [
583 "added": [
578 "<sha1_hash>"
584 "<sha1_hash>"
579 ],
585 ],
580 "common": [
586 "common": [
581 "<sha1_hash>",
587 "<sha1_hash>",
582 "<sha1_hash>",
588 "<sha1_hash>",
583 ],
589 ],
584 "removed": []
590 "removed": []
585 }
591 }
586 }
592 }
587 error : null
593 error : null
588 """
594 """
589
595
590 repo = get_repo_or_error(repoid)
596 repo = get_repo_or_error(repoid)
591 pull_request = get_pull_request_or_error(pullrequestid)
597 pull_request = get_pull_request_or_error(pullrequestid)
592 if not PullRequestModel().check_user_update(
598 if not PullRequestModel().check_user_update(
593 pull_request, apiuser, api=True):
599 pull_request, apiuser, api=True):
594 raise JSONRPCError(
600 raise JSONRPCError(
595 'pull request `%s` update failed, no permission to update.' % (
601 'pull request `%s` update failed, no permission to update.' % (
596 pullrequestid,))
602 pullrequestid,))
597 if pull_request.is_closed():
603 if pull_request.is_closed():
598 raise JSONRPCError(
604 raise JSONRPCError(
599 'pull request `%s` update failed, pull request is closed' % (
605 'pull request `%s` update failed, pull request is closed' % (
600 pullrequestid,))
606 pullrequestid,))
601
607
602 reviewer_objects = Optional.extract(reviewers) or []
608 reviewer_objects = Optional.extract(reviewers) or []
603 if not isinstance(reviewer_objects, list):
609 if not isinstance(reviewer_objects, list):
604 raise JSONRPCError('reviewers should be specified as a list')
610 raise JSONRPCError('reviewers should be specified as a list')
605
611
606 reviewers_reasons = []
612 reviewers_reasons = []
607 reviewer_ids = set()
613 reviewer_ids = set()
608 for reviewer_object in reviewer_objects:
614 for reviewer_object in reviewer_objects:
609 reviewer_reasons = []
615 reviewer_reasons = []
610 if isinstance(reviewer_object, (int, basestring)):
616 if isinstance(reviewer_object, (int, basestring)):
611 reviewer_username = reviewer_object
617 reviewer_username = reviewer_object
612 else:
618 else:
613 reviewer_username = reviewer_object['username']
619 reviewer_username = reviewer_object['username']
614 reviewer_reasons = reviewer_object.get('reasons', [])
620 reviewer_reasons = reviewer_object.get('reasons', [])
615
621
616 user = get_user_or_error(reviewer_username)
622 user = get_user_or_error(reviewer_username)
617 reviewer_ids.add(user.user_id)
623 reviewer_ids.add(user.user_id)
618 reviewers_reasons.append((user.user_id, reviewer_reasons))
624 reviewers_reasons.append((user.user_id, reviewer_reasons))
619
625
620 title = Optional.extract(title)
626 title = Optional.extract(title)
621 description = Optional.extract(description)
627 description = Optional.extract(description)
622 if title or description:
628 if title or description:
623 PullRequestModel().edit(
629 PullRequestModel().edit(
624 pull_request, title or pull_request.title,
630 pull_request, title or pull_request.title,
625 description or pull_request.description)
631 description or pull_request.description)
626 Session().commit()
632 Session().commit()
627
633
628 commit_changes = {"added": [], "common": [], "removed": []}
634 commit_changes = {"added": [], "common": [], "removed": []}
629 if str2bool(Optional.extract(update_commits)):
635 if str2bool(Optional.extract(update_commits)):
630 if PullRequestModel().has_valid_update_type(pull_request):
636 if PullRequestModel().has_valid_update_type(pull_request):
631 _version, _commit_changes = PullRequestModel().update_commits(
637 _version, _commit_changes = PullRequestModel().update_commits(
632 pull_request)
638 pull_request)
633 commit_changes = _commit_changes or commit_changes
639 commit_changes = _commit_changes or commit_changes
634 Session().commit()
640 Session().commit()
635
641
636 reviewers_changes = {"added": [], "removed": []}
642 reviewers_changes = {"added": [], "removed": []}
637 if reviewer_ids:
643 if reviewer_ids:
638 added_reviewers, removed_reviewers = \
644 added_reviewers, removed_reviewers = \
639 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
645 PullRequestModel().update_reviewers(pull_request, reviewers_reasons)
640
646
641 reviewers_changes['added'] = sorted(
647 reviewers_changes['added'] = sorted(
642 [get_user_or_error(n).username for n in added_reviewers])
648 [get_user_or_error(n).username for n in added_reviewers])
643 reviewers_changes['removed'] = sorted(
649 reviewers_changes['removed'] = sorted(
644 [get_user_or_error(n).username for n in removed_reviewers])
650 [get_user_or_error(n).username for n in removed_reviewers])
645 Session().commit()
651 Session().commit()
646
652
647 if str2bool(Optional.extract(close_pull_request)):
653 if str2bool(Optional.extract(close_pull_request)):
648 PullRequestModel().close_pull_request_with_comment(
654 PullRequestModel().close_pull_request_with_comment(
649 pull_request, apiuser, repo)
655 pull_request, apiuser, repo)
650 Session().commit()
656 Session().commit()
651
657
652 data = {
658 data = {
653 'msg': 'Updated pull request `{}`'.format(
659 'msg': 'Updated pull request `{}`'.format(
654 pull_request.pull_request_id),
660 pull_request.pull_request_id),
655 'pull_request': pull_request.get_api_data(),
661 'pull_request': pull_request.get_api_data(),
656 'updated_commits': commit_changes,
662 'updated_commits': commit_changes,
657 'updated_reviewers': reviewers_changes
663 'updated_reviewers': reviewers_changes
658 }
664 }
659 return data
665 return data
660
666
@@ -1,509 +1,512 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 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 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import importlib
28 import importlib
29 import re
29 import re
30 from functools import wraps
30 from functools import wraps
31
31
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from webob.exc import (
33 from webob.exc import (
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
35
35
36 import rhodecode
36 import rhodecode
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
42 NotAllowedToCreateUserError)
42 NotAllowedToCreateUserError)
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
44 from rhodecode.lib.middleware import appenlight
44 from rhodecode.lib.middleware import appenlight
45 from rhodecode.lib.middleware.utils import scm_app
45 from rhodecode.lib.middleware.utils import scm_app
46 from rhodecode.lib.utils import (
46 from rhodecode.lib.utils import (
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
50 from rhodecode.lib.vcs.backends import base
50 from rhodecode.lib.vcs.backends import base
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.db import User, Repository
52 from rhodecode.model.db import User, Repository
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def initialize_generator(factory):
59 def initialize_generator(factory):
60 """
60 """
61 Initializes the returned generator by draining its first element.
61 Initializes the returned generator by draining its first element.
62
62
63 This can be used to give a generator an initializer, which is the code
63 This can be used to give a generator an initializer, which is the code
64 up to the first yield statement. This decorator enforces that the first
64 up to the first yield statement. This decorator enforces that the first
65 produced element has the value ``"__init__"`` to make its special
65 produced element has the value ``"__init__"`` to make its special
66 purpose very explicit in the using code.
66 purpose very explicit in the using code.
67 """
67 """
68
68
69 @wraps(factory)
69 @wraps(factory)
70 def wrapper(*args, **kwargs):
70 def wrapper(*args, **kwargs):
71 gen = factory(*args, **kwargs)
71 gen = factory(*args, **kwargs)
72 try:
72 try:
73 init = gen.next()
73 init = gen.next()
74 except StopIteration:
74 except StopIteration:
75 raise ValueError('Generator must yield at least one element.')
75 raise ValueError('Generator must yield at least one element.')
76 if init != "__init__":
76 if init != "__init__":
77 raise ValueError('First yielded element must be "__init__".')
77 raise ValueError('First yielded element must be "__init__".')
78 return gen
78 return gen
79 return wrapper
79 return wrapper
80
80
81
81
82 class SimpleVCS(object):
82 class SimpleVCS(object):
83 """Common functionality for SCM HTTP handlers."""
83 """Common functionality for SCM HTTP handlers."""
84
84
85 SCM = 'unknown'
85 SCM = 'unknown'
86
86
87 acl_repo_name = None
87 acl_repo_name = None
88 url_repo_name = None
88 url_repo_name = None
89 vcs_repo_name = None
89 vcs_repo_name = None
90
90
91 def __init__(self, application, config, registry):
91 def __init__(self, application, config, registry):
92 self.registry = registry
92 self.registry = registry
93 self.application = application
93 self.application = application
94 self.config = config
94 self.config = config
95 # re-populated by specialized middleware
95 # re-populated by specialized middleware
96 self.repo_vcs_config = base.Config()
96 self.repo_vcs_config = base.Config()
97
97
98 # base path of repo locations
98 # base path of repo locations
99 self.basepath = get_rhodecode_base_path()
99 self.basepath = get_rhodecode_base_path()
100 # authenticate this VCS request using authfunc
100 # authenticate this VCS request using authfunc
101 auth_ret_code_detection = \
101 auth_ret_code_detection = \
102 str2bool(self.config.get('auth_ret_code_detection', False))
102 str2bool(self.config.get('auth_ret_code_detection', False))
103 self.authenticate = BasicAuth(
103 self.authenticate = BasicAuth(
104 '', authenticate, registry, config.get('auth_ret_code'),
104 '', authenticate, registry, config.get('auth_ret_code'),
105 auth_ret_code_detection)
105 auth_ret_code_detection)
106 self.ip_addr = '0.0.0.0'
106 self.ip_addr = '0.0.0.0'
107
107
108 def set_repo_names(self, environ):
108 def set_repo_names(self, environ):
109 """
109 """
110 This will populate the attributes acl_repo_name, url_repo_name,
110 This will populate the attributes acl_repo_name, url_repo_name,
111 vcs_repo_name and pr_id on the current instance.
111 vcs_repo_name and pr_id on the current instance.
112 """
112 """
113 # TODO: martinb: Unify generation/suffix of clone url. It is currently
114 # used here in the regex, in PullRequest in get_api_data() and
115 # indirectly in routing configuration.
113 # TODO: martinb: Move to class or module scope.
116 # TODO: martinb: Move to class or module scope.
114 # TODO: martinb: Check if we have to use re.UNICODE.
117 # TODO: martinb: Check if we have to use re.UNICODE.
115 # TODO: martinb: Check which chars are allowed for repo/group names.
118 # TODO: martinb: Check which chars are allowed for repo/group names.
116 # These chars are excluded: '`?=[]\;\'"<>,/~!@#$%^&*()+{}|: '
119 # These chars are excluded: '`?=[]\;\'"<>,/~!@#$%^&*()+{}|: '
117 # Code from: rhodecode/lib/utils.py:repo_name_slug()
120 # Code from: rhodecode/lib/utils.py:repo_name_slug()
118 pr_regex = re.compile(
121 pr_regex = re.compile(
119 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/' # repo groups
122 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/' # repo groups
120 '(?P<repo_name>[\w-]+)' # target repo name
123 '(?P<repo_name>[\w-]+)' # target repo name
121 '/pull-request/(?P<pr_id>\d+)/repository') # pr suffix
124 '/pull-request/(?P<pr_id>\d+)/repository') # pr suffix
122
125
123 # Get url repo name from environment.
126 # Get url repo name from environment.
124 self.url_repo_name = self._get_repository_name(environ)
127 self.url_repo_name = self._get_repository_name(environ)
125
128
126 # Check if this is a request to a shadow repository. In case of a
129 # Check if this is a request to a shadow repository. In case of a
127 # shadow repo set vcs_repo_name to the file system path pointing to the
130 # shadow repo set vcs_repo_name to the file system path pointing to the
128 # shadow repo. And set acl_repo_name to the pull request target repo
131 # shadow repo. And set acl_repo_name to the pull request target repo
129 # because we use the target repo for permission checks. Otherwise all
132 # because we use the target repo for permission checks. Otherwise all
130 # names are equal.
133 # names are equal.
131 match = pr_regex.match(self.url_repo_name)
134 match = pr_regex.match(self.url_repo_name)
132 if match:
135 if match:
133 # Get pull request instance.
136 # Get pull request instance.
134 match_dict = match.groupdict()
137 match_dict = match.groupdict()
135 pr_id = match_dict['pr_id']
138 pr_id = match_dict['pr_id']
136 pull_request = PullRequest.get(pr_id)
139 pull_request = PullRequest.get(pr_id)
137
140
138 # Get file system path to shadow repository.
141 # Get file system path to shadow repository.
139 workspace_id = PullRequestModel()._workspace_id(pull_request)
142 workspace_id = PullRequestModel()._workspace_id(pull_request)
140 target_vcs = pull_request.target_repo.scm_instance()
143 target_vcs = pull_request.target_repo.scm_instance()
141 vcs_repo_name = target_vcs._get_shadow_repository_path(
144 vcs_repo_name = target_vcs._get_shadow_repository_path(
142 workspace_id)
145 workspace_id)
143
146
144 # Store names for later usage.
147 # Store names for later usage.
145 self.pr_id = pr_id
148 self.pr_id = pr_id
146 self.vcs_repo_name = vcs_repo_name
149 self.vcs_repo_name = vcs_repo_name
147 self.acl_repo_name = pull_request.target_repo.repo_name
150 self.acl_repo_name = pull_request.target_repo.repo_name
148 else:
151 else:
149 # All names are equal for normal (non shadow) repositories.
152 # All names are equal for normal (non shadow) repositories.
150 self.acl_repo_name = self.url_repo_name
153 self.acl_repo_name = self.url_repo_name
151 self.vcs_repo_name = self.url_repo_name
154 self.vcs_repo_name = self.url_repo_name
152 self.pr_id = None
155 self.pr_id = None
153
156
154 @property
157 @property
155 def repo_name(self):
158 def repo_name(self):
156 # TODO: johbo: Remove, switch to correct repo name attribute
159 # TODO: johbo: Remove, switch to correct repo name attribute
157 return self.acl_repo_name
160 return self.acl_repo_name
158
161
159 @property
162 @property
160 def scm_app(self):
163 def scm_app(self):
161 custom_implementation = self.config.get('vcs.scm_app_implementation')
164 custom_implementation = self.config.get('vcs.scm_app_implementation')
162 if custom_implementation and custom_implementation != 'pyro4':
165 if custom_implementation and custom_implementation != 'pyro4':
163 log.info(
166 log.info(
164 "Using custom implementation of scm_app: %s",
167 "Using custom implementation of scm_app: %s",
165 custom_implementation)
168 custom_implementation)
166 scm_app_impl = importlib.import_module(custom_implementation)
169 scm_app_impl = importlib.import_module(custom_implementation)
167 else:
170 else:
168 scm_app_impl = scm_app
171 scm_app_impl = scm_app
169 return scm_app_impl
172 return scm_app_impl
170
173
171 def _get_by_id(self, repo_name):
174 def _get_by_id(self, repo_name):
172 """
175 """
173 Gets a special pattern _<ID> from clone url and tries to replace it
176 Gets a special pattern _<ID> from clone url and tries to replace it
174 with a repository_name for support of _<ID> non changeable urls
177 with a repository_name for support of _<ID> non changeable urls
175 """
178 """
176
179
177 data = repo_name.split('/')
180 data = repo_name.split('/')
178 if len(data) >= 2:
181 if len(data) >= 2:
179 from rhodecode.model.repo import RepoModel
182 from rhodecode.model.repo import RepoModel
180 by_id_match = RepoModel().get_repo_by_id(repo_name)
183 by_id_match = RepoModel().get_repo_by_id(repo_name)
181 if by_id_match:
184 if by_id_match:
182 data[1] = by_id_match.repo_name
185 data[1] = by_id_match.repo_name
183
186
184 return safe_str('/'.join(data))
187 return safe_str('/'.join(data))
185
188
186 def _invalidate_cache(self, repo_name):
189 def _invalidate_cache(self, repo_name):
187 """
190 """
188 Set's cache for this repository for invalidation on next access
191 Set's cache for this repository for invalidation on next access
189
192
190 :param repo_name: full repo name, also a cache key
193 :param repo_name: full repo name, also a cache key
191 """
194 """
192 ScmModel().mark_for_invalidation(repo_name)
195 ScmModel().mark_for_invalidation(repo_name)
193
196
194 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
197 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
195 db_repo = Repository.get_by_repo_name(repo_name)
198 db_repo = Repository.get_by_repo_name(repo_name)
196 if not db_repo:
199 if not db_repo:
197 log.debug('Repository `%s` not found inside the database.',
200 log.debug('Repository `%s` not found inside the database.',
198 repo_name)
201 repo_name)
199 return False
202 return False
200
203
201 if db_repo.repo_type != scm_type:
204 if db_repo.repo_type != scm_type:
202 log.warning(
205 log.warning(
203 'Repository `%s` have incorrect scm_type, expected %s got %s',
206 'Repository `%s` have incorrect scm_type, expected %s got %s',
204 repo_name, db_repo.repo_type, scm_type)
207 repo_name, db_repo.repo_type, scm_type)
205 return False
208 return False
206
209
207 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
210 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
208
211
209 def valid_and_active_user(self, user):
212 def valid_and_active_user(self, user):
210 """
213 """
211 Checks if that user is not empty, and if it's actually object it checks
214 Checks if that user is not empty, and if it's actually object it checks
212 if he's active.
215 if he's active.
213
216
214 :param user: user object or None
217 :param user: user object or None
215 :return: boolean
218 :return: boolean
216 """
219 """
217 if user is None:
220 if user is None:
218 return False
221 return False
219
222
220 elif user.active:
223 elif user.active:
221 return True
224 return True
222
225
223 return False
226 return False
224
227
225 def _check_permission(self, action, user, repo_name, ip_addr=None):
228 def _check_permission(self, action, user, repo_name, ip_addr=None):
226 """
229 """
227 Checks permissions using action (push/pull) user and repository
230 Checks permissions using action (push/pull) user and repository
228 name
231 name
229
232
230 :param action: push or pull action
233 :param action: push or pull action
231 :param user: user instance
234 :param user: user instance
232 :param repo_name: repository name
235 :param repo_name: repository name
233 """
236 """
234 # check IP
237 # check IP
235 inherit = user.inherit_default_permissions
238 inherit = user.inherit_default_permissions
236 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
239 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
237 inherit_from_default=inherit)
240 inherit_from_default=inherit)
238 if ip_allowed:
241 if ip_allowed:
239 log.info('Access for IP:%s allowed', ip_addr)
242 log.info('Access for IP:%s allowed', ip_addr)
240 else:
243 else:
241 return False
244 return False
242
245
243 if action == 'push':
246 if action == 'push':
244 if not HasPermissionAnyMiddleware('repository.write',
247 if not HasPermissionAnyMiddleware('repository.write',
245 'repository.admin')(user,
248 'repository.admin')(user,
246 repo_name):
249 repo_name):
247 return False
250 return False
248
251
249 else:
252 else:
250 # any other action need at least read permission
253 # any other action need at least read permission
251 if not HasPermissionAnyMiddleware('repository.read',
254 if not HasPermissionAnyMiddleware('repository.read',
252 'repository.write',
255 'repository.write',
253 'repository.admin')(user,
256 'repository.admin')(user,
254 repo_name):
257 repo_name):
255 return False
258 return False
256
259
257 return True
260 return True
258
261
259 def _check_ssl(self, environ, start_response):
262 def _check_ssl(self, environ, start_response):
260 """
263 """
261 Checks the SSL check flag and returns False if SSL is not present
264 Checks the SSL check flag and returns False if SSL is not present
262 and required True otherwise
265 and required True otherwise
263 """
266 """
264 org_proto = environ['wsgi._org_proto']
267 org_proto = environ['wsgi._org_proto']
265 # check if we have SSL required ! if not it's a bad request !
268 # check if we have SSL required ! if not it's a bad request !
266 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
269 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
267 if require_ssl and org_proto == 'http':
270 if require_ssl and org_proto == 'http':
268 log.debug('proto is %s and SSL is required BAD REQUEST !',
271 log.debug('proto is %s and SSL is required BAD REQUEST !',
269 org_proto)
272 org_proto)
270 return False
273 return False
271 return True
274 return True
272
275
273 def __call__(self, environ, start_response):
276 def __call__(self, environ, start_response):
274 try:
277 try:
275 return self._handle_request(environ, start_response)
278 return self._handle_request(environ, start_response)
276 except Exception:
279 except Exception:
277 log.exception("Exception while handling request")
280 log.exception("Exception while handling request")
278 appenlight.track_exception(environ)
281 appenlight.track_exception(environ)
279 return HTTPInternalServerError()(environ, start_response)
282 return HTTPInternalServerError()(environ, start_response)
280 finally:
283 finally:
281 meta.Session.remove()
284 meta.Session.remove()
282
285
283 def _handle_request(self, environ, start_response):
286 def _handle_request(self, environ, start_response):
284
287
285 if not self._check_ssl(environ, start_response):
288 if not self._check_ssl(environ, start_response):
286 reason = ('SSL required, while RhodeCode was unable '
289 reason = ('SSL required, while RhodeCode was unable '
287 'to detect this as SSL request')
290 'to detect this as SSL request')
288 log.debug('User not allowed to proceed, %s', reason)
291 log.debug('User not allowed to proceed, %s', reason)
289 return HTTPNotAcceptable(reason)(environ, start_response)
292 return HTTPNotAcceptable(reason)(environ, start_response)
290
293
291 if not self.repo_name:
294 if not self.repo_name:
292 log.warning('Repository name is empty: %s', self.repo_name)
295 log.warning('Repository name is empty: %s', self.repo_name)
293 # failed to get repo name, we fail now
296 # failed to get repo name, we fail now
294 return HTTPNotFound()(environ, start_response)
297 return HTTPNotFound()(environ, start_response)
295 log.debug('Extracted repo name is %s', self.repo_name)
298 log.debug('Extracted repo name is %s', self.repo_name)
296
299
297 ip_addr = get_ip_addr(environ)
300 ip_addr = get_ip_addr(environ)
298 username = None
301 username = None
299
302
300 # skip passing error to error controller
303 # skip passing error to error controller
301 environ['pylons.status_code_redirect'] = True
304 environ['pylons.status_code_redirect'] = True
302
305
303 # ======================================================================
306 # ======================================================================
304 # GET ACTION PULL or PUSH
307 # GET ACTION PULL or PUSH
305 # ======================================================================
308 # ======================================================================
306 action = self._get_action(environ)
309 action = self._get_action(environ)
307
310
308 # ======================================================================
311 # ======================================================================
309 # Check if this is a request to a shadow repository of a pull request.
312 # Check if this is a request to a shadow repository of a pull request.
310 # In this case only pull action is allowed.
313 # In this case only pull action is allowed.
311 # ======================================================================
314 # ======================================================================
312 if self.pr_id is not None and action != 'pull':
315 if self.pr_id is not None and action != 'pull':
313 reason = 'Only pull action is allowed for shadow repositories.'
316 reason = 'Only pull action is allowed for shadow repositories.'
314 log.debug('User not allowed to proceed, %s', reason)
317 log.debug('User not allowed to proceed, %s', reason)
315 return HTTPNotAcceptable(reason)(environ, start_response)
318 return HTTPNotAcceptable(reason)(environ, start_response)
316
319
317 # ======================================================================
320 # ======================================================================
318 # CHECK ANONYMOUS PERMISSION
321 # CHECK ANONYMOUS PERMISSION
319 # ======================================================================
322 # ======================================================================
320 if action in ['pull', 'push']:
323 if action in ['pull', 'push']:
321 anonymous_user = User.get_default_user()
324 anonymous_user = User.get_default_user()
322 username = anonymous_user.username
325 username = anonymous_user.username
323 if anonymous_user.active:
326 if anonymous_user.active:
324 # ONLY check permissions if the user is activated
327 # ONLY check permissions if the user is activated
325 anonymous_perm = self._check_permission(
328 anonymous_perm = self._check_permission(
326 action, anonymous_user, self.repo_name, ip_addr)
329 action, anonymous_user, self.repo_name, ip_addr)
327 else:
330 else:
328 anonymous_perm = False
331 anonymous_perm = False
329
332
330 if not anonymous_user.active or not anonymous_perm:
333 if not anonymous_user.active or not anonymous_perm:
331 if not anonymous_user.active:
334 if not anonymous_user.active:
332 log.debug('Anonymous access is disabled, running '
335 log.debug('Anonymous access is disabled, running '
333 'authentication')
336 'authentication')
334
337
335 if not anonymous_perm:
338 if not anonymous_perm:
336 log.debug('Not enough credentials to access this '
339 log.debug('Not enough credentials to access this '
337 'repository as anonymous user')
340 'repository as anonymous user')
338
341
339 username = None
342 username = None
340 # ==============================================================
343 # ==============================================================
341 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
344 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
342 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
345 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
343 # ==============================================================
346 # ==============================================================
344
347
345 # try to auth based on environ, container auth methods
348 # try to auth based on environ, container auth methods
346 log.debug('Running PRE-AUTH for container based authentication')
349 log.debug('Running PRE-AUTH for container based authentication')
347 pre_auth = authenticate(
350 pre_auth = authenticate(
348 '', '', environ, VCS_TYPE, registry=self.registry)
351 '', '', environ, VCS_TYPE, registry=self.registry)
349 if pre_auth and pre_auth.get('username'):
352 if pre_auth and pre_auth.get('username'):
350 username = pre_auth['username']
353 username = pre_auth['username']
351 log.debug('PRE-AUTH got %s as username', username)
354 log.debug('PRE-AUTH got %s as username', username)
352
355
353 # If not authenticated by the container, running basic auth
356 # If not authenticated by the container, running basic auth
354 if not username:
357 if not username:
355 self.authenticate.realm = get_rhodecode_realm()
358 self.authenticate.realm = get_rhodecode_realm()
356
359
357 try:
360 try:
358 result = self.authenticate(environ)
361 result = self.authenticate(environ)
359 except (UserCreationError, NotAllowedToCreateUserError) as e:
362 except (UserCreationError, NotAllowedToCreateUserError) as e:
360 log.error(e)
363 log.error(e)
361 reason = safe_str(e)
364 reason = safe_str(e)
362 return HTTPNotAcceptable(reason)(environ, start_response)
365 return HTTPNotAcceptable(reason)(environ, start_response)
363
366
364 if isinstance(result, str):
367 if isinstance(result, str):
365 AUTH_TYPE.update(environ, 'basic')
368 AUTH_TYPE.update(environ, 'basic')
366 REMOTE_USER.update(environ, result)
369 REMOTE_USER.update(environ, result)
367 username = result
370 username = result
368 else:
371 else:
369 return result.wsgi_application(environ, start_response)
372 return result.wsgi_application(environ, start_response)
370
373
371 # ==============================================================
374 # ==============================================================
372 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
375 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
373 # ==============================================================
376 # ==============================================================
374 user = User.get_by_username(username)
377 user = User.get_by_username(username)
375 if not self.valid_and_active_user(user):
378 if not self.valid_and_active_user(user):
376 return HTTPForbidden()(environ, start_response)
379 return HTTPForbidden()(environ, start_response)
377 username = user.username
380 username = user.username
378 user.update_lastactivity()
381 user.update_lastactivity()
379 meta.Session().commit()
382 meta.Session().commit()
380
383
381 # check user attributes for password change flag
384 # check user attributes for password change flag
382 user_obj = user
385 user_obj = user
383 if user_obj and user_obj.username != User.DEFAULT_USER and \
386 if user_obj and user_obj.username != User.DEFAULT_USER and \
384 user_obj.user_data.get('force_password_change'):
387 user_obj.user_data.get('force_password_change'):
385 reason = 'password change required'
388 reason = 'password change required'
386 log.debug('User not allowed to authenticate, %s', reason)
389 log.debug('User not allowed to authenticate, %s', reason)
387 return HTTPNotAcceptable(reason)(environ, start_response)
390 return HTTPNotAcceptable(reason)(environ, start_response)
388
391
389 # check permissions for this repository
392 # check permissions for this repository
390 perm = self._check_permission(
393 perm = self._check_permission(
391 action, user, self.repo_name, ip_addr)
394 action, user, self.repo_name, ip_addr)
392 if not perm:
395 if not perm:
393 return HTTPForbidden()(environ, start_response)
396 return HTTPForbidden()(environ, start_response)
394
397
395 # extras are injected into UI object and later available
398 # extras are injected into UI object and later available
396 # in hooks executed by rhodecode
399 # in hooks executed by rhodecode
397 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
400 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
398 extras = vcs_operation_context(
401 extras = vcs_operation_context(
399 environ, repo_name=self.repo_name, username=username,
402 environ, repo_name=self.repo_name, username=username,
400 action=action, scm=self.SCM,
403 action=action, scm=self.SCM,
401 check_locking=check_locking)
404 check_locking=check_locking)
402
405
403 # ======================================================================
406 # ======================================================================
404 # REQUEST HANDLING
407 # REQUEST HANDLING
405 # ======================================================================
408 # ======================================================================
406 str_repo_name = safe_str(self.repo_name)
409 str_repo_name = safe_str(self.repo_name)
407 repo_path = os.path.join(
410 repo_path = os.path.join(
408 safe_str(self.basepath), safe_str(self.vcs_repo_name))
411 safe_str(self.basepath), safe_str(self.vcs_repo_name))
409 log.debug('Repository path is %s', repo_path)
412 log.debug('Repository path is %s', repo_path)
410
413
411 fix_PATH()
414 fix_PATH()
412
415
413 log.info(
416 log.info(
414 '%s action on %s repo "%s" by "%s" from %s',
417 '%s action on %s repo "%s" by "%s" from %s',
415 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
418 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
416
419
417 return self._generate_vcs_response(
420 return self._generate_vcs_response(
418 environ, start_response, repo_path, self.url_repo_name, extras, action)
421 environ, start_response, repo_path, self.url_repo_name, extras, action)
419
422
420 @initialize_generator
423 @initialize_generator
421 def _generate_vcs_response(
424 def _generate_vcs_response(
422 self, environ, start_response, repo_path, repo_name, extras,
425 self, environ, start_response, repo_path, repo_name, extras,
423 action):
426 action):
424 """
427 """
425 Returns a generator for the response content.
428 Returns a generator for the response content.
426
429
427 This method is implemented as a generator, so that it can trigger
430 This method is implemented as a generator, so that it can trigger
428 the cache validation after all content sent back to the client. It
431 the cache validation after all content sent back to the client. It
429 also handles the locking exceptions which will be triggered when
432 also handles the locking exceptions which will be triggered when
430 the first chunk is produced by the underlying WSGI application.
433 the first chunk is produced by the underlying WSGI application.
431 """
434 """
432 callback_daemon, extras = self._prepare_callback_daemon(extras)
435 callback_daemon, extras = self._prepare_callback_daemon(extras)
433 config = self._create_config(extras, self.acl_repo_name)
436 config = self._create_config(extras, self.acl_repo_name)
434 log.debug('HOOKS extras is %s', extras)
437 log.debug('HOOKS extras is %s', extras)
435 app = self._create_wsgi_app(repo_path, repo_name, config)
438 app = self._create_wsgi_app(repo_path, repo_name, config)
436
439
437 try:
440 try:
438 with callback_daemon:
441 with callback_daemon:
439 try:
442 try:
440 response = app(environ, start_response)
443 response = app(environ, start_response)
441 finally:
444 finally:
442 # This statement works together with the decorator
445 # This statement works together with the decorator
443 # "initialize_generator" above. The decorator ensures that
446 # "initialize_generator" above. The decorator ensures that
444 # we hit the first yield statement before the generator is
447 # we hit the first yield statement before the generator is
445 # returned back to the WSGI server. This is needed to
448 # returned back to the WSGI server. This is needed to
446 # ensure that the call to "app" above triggers the
449 # ensure that the call to "app" above triggers the
447 # needed callback to "start_response" before the
450 # needed callback to "start_response" before the
448 # generator is actually used.
451 # generator is actually used.
449 yield "__init__"
452 yield "__init__"
450
453
451 for chunk in response:
454 for chunk in response:
452 yield chunk
455 yield chunk
453 except Exception as exc:
456 except Exception as exc:
454 # TODO: johbo: Improve "translating" back the exception.
457 # TODO: johbo: Improve "translating" back the exception.
455 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
458 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
456 exc = HTTPLockedRC(*exc.args)
459 exc = HTTPLockedRC(*exc.args)
457 _code = rhodecode.CONFIG.get('lock_ret_code')
460 _code = rhodecode.CONFIG.get('lock_ret_code')
458 log.debug('Repository LOCKED ret code %s!', (_code,))
461 log.debug('Repository LOCKED ret code %s!', (_code,))
459 elif getattr(exc, '_vcs_kind', None) == 'requirement':
462 elif getattr(exc, '_vcs_kind', None) == 'requirement':
460 log.debug(
463 log.debug(
461 'Repository requires features unknown to this Mercurial')
464 'Repository requires features unknown to this Mercurial')
462 exc = HTTPRequirementError(*exc.args)
465 exc = HTTPRequirementError(*exc.args)
463 else:
466 else:
464 raise
467 raise
465
468
466 for chunk in exc(environ, start_response):
469 for chunk in exc(environ, start_response):
467 yield chunk
470 yield chunk
468 finally:
471 finally:
469 # invalidate cache on push
472 # invalidate cache on push
470 try:
473 try:
471 if action == 'push':
474 if action == 'push':
472 self._invalidate_cache(repo_name)
475 self._invalidate_cache(repo_name)
473 finally:
476 finally:
474 meta.Session.remove()
477 meta.Session.remove()
475
478
476 def _get_repository_name(self, environ):
479 def _get_repository_name(self, environ):
477 """Get repository name out of the environmnent
480 """Get repository name out of the environmnent
478
481
479 :param environ: WSGI environment
482 :param environ: WSGI environment
480 """
483 """
481 raise NotImplementedError()
484 raise NotImplementedError()
482
485
483 def _get_action(self, environ):
486 def _get_action(self, environ):
484 """Map request commands into a pull or push command.
487 """Map request commands into a pull or push command.
485
488
486 :param environ: WSGI environment
489 :param environ: WSGI environment
487 """
490 """
488 raise NotImplementedError()
491 raise NotImplementedError()
489
492
490 def _create_wsgi_app(self, repo_path, repo_name, config):
493 def _create_wsgi_app(self, repo_path, repo_name, config):
491 """Return the WSGI app that will finally handle the request."""
494 """Return the WSGI app that will finally handle the request."""
492 raise NotImplementedError()
495 raise NotImplementedError()
493
496
494 def _create_config(self, extras, repo_name):
497 def _create_config(self, extras, repo_name):
495 """Create a Pyro safe config representation."""
498 """Create a Pyro safe config representation."""
496 raise NotImplementedError()
499 raise NotImplementedError()
497
500
498 def _prepare_callback_daemon(self, extras):
501 def _prepare_callback_daemon(self, extras):
499 return prepare_callback_daemon(
502 return prepare_callback_daemon(
500 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
503 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
501 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
504 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
502
505
503
506
504 def _should_check_locking(query_string):
507 def _should_check_locking(query_string):
505 # this is kind of hacky, but due to how mercurial handles client-server
508 # this is kind of hacky, but due to how mercurial handles client-server
506 # server see all operation on commit; bookmarks, phases and
509 # server see all operation on commit; bookmarks, phases and
507 # obsolescence marker in different transaction, we don't want to check
510 # obsolescence marker in different transaction, we don't want to check
508 # locking on those
511 # locking on those
509 return query_string not in ['cmd=listkeys']
512 return query_string not in ['cmd=listkeys']
@@ -1,3658 +1,3663 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import sys
27 import sys
28 import time
28 import time
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import warnings
32 import warnings
33 import ipaddress
33 import ipaddress
34 import functools
34 import functools
35 import traceback
35 import traceback
36 import collections
36 import collections
37
37
38
38
39 from sqlalchemy import *
39 from sqlalchemy import *
40 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.exc import IntegrityError
41 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.declarative import declared_attr
42 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.ext.hybrid import hybrid_property
43 from sqlalchemy.orm import (
43 from sqlalchemy.orm import (
44 relationship, joinedload, class_mapper, validates, aliased)
44 relationship, joinedload, class_mapper, validates, aliased)
45 from sqlalchemy.sql.expression import true
45 from sqlalchemy.sql.expression import true
46 from beaker.cache import cache_region, region_invalidate
46 from beaker.cache import cache_region, region_invalidate
47 from webob.exc import HTTPNotFound
47 from webob.exc import HTTPNotFound
48 from zope.cachedescriptors.property import Lazy as LazyProperty
48 from zope.cachedescriptors.property import Lazy as LazyProperty
49
49
50 from pylons import url
50 from pylons import url
51 from pylons.i18n.translation import lazy_ugettext as _
51 from pylons.i18n.translation import lazy_ugettext as _
52
52
53 from rhodecode.lib.vcs import get_backend, get_vcs_instance
53 from rhodecode.lib.vcs import get_backend, get_vcs_instance
54 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.utils.helpers import get_scm
55 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.exceptions import VCSError
56 from rhodecode.lib.vcs.backends.base import (
56 from rhodecode.lib.vcs.backends.base import (
57 EmptyCommit, Reference, MergeFailureReason)
57 EmptyCommit, Reference, MergeFailureReason)
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re)
61 glob2re)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, JSONDict
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, JSONDict
63 from rhodecode.lib.ext_json import json
63 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.caching_query import FromCache
64 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.encrypt import AESCipher
65 from rhodecode.lib.encrypt import AESCipher
66
66
67 from rhodecode.model.meta import Base, Session
67 from rhodecode.model.meta import Base, Session
68
68
69 URL_SEP = '/'
69 URL_SEP = '/'
70 log = logging.getLogger(__name__)
70 log = logging.getLogger(__name__)
71
71
72 # =============================================================================
72 # =============================================================================
73 # BASE CLASSES
73 # BASE CLASSES
74 # =============================================================================
74 # =============================================================================
75
75
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # beaker.session.secret if first is not set.
77 # beaker.session.secret if first is not set.
78 # and initialized at environment.py
78 # and initialized at environment.py
79 ENCRYPTION_KEY = None
79 ENCRYPTION_KEY = None
80
80
81 # used to sort permissions by types, '#' used here is not allowed to be in
81 # used to sort permissions by types, '#' used here is not allowed to be in
82 # usernames, and it's very early in sorted string.printable table.
82 # usernames, and it's very early in sorted string.printable table.
83 PERMISSION_TYPE_SORT = {
83 PERMISSION_TYPE_SORT = {
84 'admin': '####',
84 'admin': '####',
85 'write': '###',
85 'write': '###',
86 'read': '##',
86 'read': '##',
87 'none': '#',
87 'none': '#',
88 }
88 }
89
89
90
90
91 def display_sort(obj):
91 def display_sort(obj):
92 """
92 """
93 Sort function used to sort permissions in .permissions() function of
93 Sort function used to sort permissions in .permissions() function of
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 of all other resources
95 of all other resources
96 """
96 """
97
97
98 if obj.username == User.DEFAULT_USER:
98 if obj.username == User.DEFAULT_USER:
99 return '#####'
99 return '#####'
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 return prefix + obj.username
101 return prefix + obj.username
102
102
103
103
104 def _hash_key(k):
104 def _hash_key(k):
105 return md5_safe(k)
105 return md5_safe(k)
106
106
107
107
108 class EncryptedTextValue(TypeDecorator):
108 class EncryptedTextValue(TypeDecorator):
109 """
109 """
110 Special column for encrypted long text data, use like::
110 Special column for encrypted long text data, use like::
111
111
112 value = Column("encrypted_value", EncryptedValue(), nullable=False)
112 value = Column("encrypted_value", EncryptedValue(), nullable=False)
113
113
114 This column is intelligent so if value is in unencrypted form it return
114 This column is intelligent so if value is in unencrypted form it return
115 unencrypted form, but on save it always encrypts
115 unencrypted form, but on save it always encrypts
116 """
116 """
117 impl = Text
117 impl = Text
118
118
119 def process_bind_param(self, value, dialect):
119 def process_bind_param(self, value, dialect):
120 if not value:
120 if not value:
121 return value
121 return value
122 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
122 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
123 # protect against double encrypting if someone manually starts
123 # protect against double encrypting if someone manually starts
124 # doing
124 # doing
125 raise ValueError('value needs to be in unencrypted format, ie. '
125 raise ValueError('value needs to be in unencrypted format, ie. '
126 'not starting with enc$aes')
126 'not starting with enc$aes')
127 return 'enc$aes_hmac$%s' % AESCipher(
127 return 'enc$aes_hmac$%s' % AESCipher(
128 ENCRYPTION_KEY, hmac=True).encrypt(value)
128 ENCRYPTION_KEY, hmac=True).encrypt(value)
129
129
130 def process_result_value(self, value, dialect):
130 def process_result_value(self, value, dialect):
131 import rhodecode
131 import rhodecode
132
132
133 if not value:
133 if not value:
134 return value
134 return value
135
135
136 parts = value.split('$', 3)
136 parts = value.split('$', 3)
137 if not len(parts) == 3:
137 if not len(parts) == 3:
138 # probably not encrypted values
138 # probably not encrypted values
139 return value
139 return value
140 else:
140 else:
141 if parts[0] != 'enc':
141 if parts[0] != 'enc':
142 # parts ok but without our header ?
142 # parts ok but without our header ?
143 return value
143 return value
144 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
144 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
145 'rhodecode.encrypted_values.strict') or True)
145 'rhodecode.encrypted_values.strict') or True)
146 # at that stage we know it's our encryption
146 # at that stage we know it's our encryption
147 if parts[1] == 'aes':
147 if parts[1] == 'aes':
148 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
148 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
149 elif parts[1] == 'aes_hmac':
149 elif parts[1] == 'aes_hmac':
150 decrypted_data = AESCipher(
150 decrypted_data = AESCipher(
151 ENCRYPTION_KEY, hmac=True,
151 ENCRYPTION_KEY, hmac=True,
152 strict_verification=enc_strict_mode).decrypt(parts[2])
152 strict_verification=enc_strict_mode).decrypt(parts[2])
153 else:
153 else:
154 raise ValueError(
154 raise ValueError(
155 'Encryption type part is wrong, must be `aes` '
155 'Encryption type part is wrong, must be `aes` '
156 'or `aes_hmac`, got `%s` instead' % (parts[1]))
156 'or `aes_hmac`, got `%s` instead' % (parts[1]))
157 return decrypted_data
157 return decrypted_data
158
158
159
159
160 class BaseModel(object):
160 class BaseModel(object):
161 """
161 """
162 Base Model for all classes
162 Base Model for all classes
163 """
163 """
164
164
165 @classmethod
165 @classmethod
166 def _get_keys(cls):
166 def _get_keys(cls):
167 """return column names for this model """
167 """return column names for this model """
168 return class_mapper(cls).c.keys()
168 return class_mapper(cls).c.keys()
169
169
170 def get_dict(self):
170 def get_dict(self):
171 """
171 """
172 return dict with keys and values corresponding
172 return dict with keys and values corresponding
173 to this model data """
173 to this model data """
174
174
175 d = {}
175 d = {}
176 for k in self._get_keys():
176 for k in self._get_keys():
177 d[k] = getattr(self, k)
177 d[k] = getattr(self, k)
178
178
179 # also use __json__() if present to get additional fields
179 # also use __json__() if present to get additional fields
180 _json_attr = getattr(self, '__json__', None)
180 _json_attr = getattr(self, '__json__', None)
181 if _json_attr:
181 if _json_attr:
182 # update with attributes from __json__
182 # update with attributes from __json__
183 if callable(_json_attr):
183 if callable(_json_attr):
184 _json_attr = _json_attr()
184 _json_attr = _json_attr()
185 for k, val in _json_attr.iteritems():
185 for k, val in _json_attr.iteritems():
186 d[k] = val
186 d[k] = val
187 return d
187 return d
188
188
189 def get_appstruct(self):
189 def get_appstruct(self):
190 """return list with keys and values tuples corresponding
190 """return list with keys and values tuples corresponding
191 to this model data """
191 to this model data """
192
192
193 l = []
193 l = []
194 for k in self._get_keys():
194 for k in self._get_keys():
195 l.append((k, getattr(self, k),))
195 l.append((k, getattr(self, k),))
196 return l
196 return l
197
197
198 def populate_obj(self, populate_dict):
198 def populate_obj(self, populate_dict):
199 """populate model with data from given populate_dict"""
199 """populate model with data from given populate_dict"""
200
200
201 for k in self._get_keys():
201 for k in self._get_keys():
202 if k in populate_dict:
202 if k in populate_dict:
203 setattr(self, k, populate_dict[k])
203 setattr(self, k, populate_dict[k])
204
204
205 @classmethod
205 @classmethod
206 def query(cls):
206 def query(cls):
207 return Session().query(cls)
207 return Session().query(cls)
208
208
209 @classmethod
209 @classmethod
210 def get(cls, id_):
210 def get(cls, id_):
211 if id_:
211 if id_:
212 return cls.query().get(id_)
212 return cls.query().get(id_)
213
213
214 @classmethod
214 @classmethod
215 def get_or_404(cls, id_):
215 def get_or_404(cls, id_):
216 try:
216 try:
217 id_ = int(id_)
217 id_ = int(id_)
218 except (TypeError, ValueError):
218 except (TypeError, ValueError):
219 raise HTTPNotFound
219 raise HTTPNotFound
220
220
221 res = cls.query().get(id_)
221 res = cls.query().get(id_)
222 if not res:
222 if not res:
223 raise HTTPNotFound
223 raise HTTPNotFound
224 return res
224 return res
225
225
226 @classmethod
226 @classmethod
227 def getAll(cls):
227 def getAll(cls):
228 # deprecated and left for backward compatibility
228 # deprecated and left for backward compatibility
229 return cls.get_all()
229 return cls.get_all()
230
230
231 @classmethod
231 @classmethod
232 def get_all(cls):
232 def get_all(cls):
233 return cls.query().all()
233 return cls.query().all()
234
234
235 @classmethod
235 @classmethod
236 def delete(cls, id_):
236 def delete(cls, id_):
237 obj = cls.query().get(id_)
237 obj = cls.query().get(id_)
238 Session().delete(obj)
238 Session().delete(obj)
239
239
240 @classmethod
240 @classmethod
241 def identity_cache(cls, session, attr_name, value):
241 def identity_cache(cls, session, attr_name, value):
242 exist_in_session = []
242 exist_in_session = []
243 for (item_cls, pkey), instance in session.identity_map.items():
243 for (item_cls, pkey), instance in session.identity_map.items():
244 if cls == item_cls and getattr(instance, attr_name) == value:
244 if cls == item_cls and getattr(instance, attr_name) == value:
245 exist_in_session.append(instance)
245 exist_in_session.append(instance)
246 if exist_in_session:
246 if exist_in_session:
247 if len(exist_in_session) == 1:
247 if len(exist_in_session) == 1:
248 return exist_in_session[0]
248 return exist_in_session[0]
249 log.exception(
249 log.exception(
250 'multiple objects with attr %s and '
250 'multiple objects with attr %s and '
251 'value %s found with same name: %r',
251 'value %s found with same name: %r',
252 attr_name, value, exist_in_session)
252 attr_name, value, exist_in_session)
253
253
254 def __repr__(self):
254 def __repr__(self):
255 if hasattr(self, '__unicode__'):
255 if hasattr(self, '__unicode__'):
256 # python repr needs to return str
256 # python repr needs to return str
257 try:
257 try:
258 return safe_str(self.__unicode__())
258 return safe_str(self.__unicode__())
259 except UnicodeDecodeError:
259 except UnicodeDecodeError:
260 pass
260 pass
261 return '<DB:%s>' % (self.__class__.__name__)
261 return '<DB:%s>' % (self.__class__.__name__)
262
262
263
263
264 class RhodeCodeSetting(Base, BaseModel):
264 class RhodeCodeSetting(Base, BaseModel):
265 __tablename__ = 'rhodecode_settings'
265 __tablename__ = 'rhodecode_settings'
266 __table_args__ = (
266 __table_args__ = (
267 UniqueConstraint('app_settings_name'),
267 UniqueConstraint('app_settings_name'),
268 {'extend_existing': True, 'mysql_engine': 'InnoDB',
268 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
269 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 )
270 )
271
271
272 SETTINGS_TYPES = {
272 SETTINGS_TYPES = {
273 'str': safe_str,
273 'str': safe_str,
274 'int': safe_int,
274 'int': safe_int,
275 'unicode': safe_unicode,
275 'unicode': safe_unicode,
276 'bool': str2bool,
276 'bool': str2bool,
277 'list': functools.partial(aslist, sep=',')
277 'list': functools.partial(aslist, sep=',')
278 }
278 }
279 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
279 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 GLOBAL_CONF_KEY = 'app_settings'
280 GLOBAL_CONF_KEY = 'app_settings'
281
281
282 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
282 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
283 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
284 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
285 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286
286
287 def __init__(self, key='', val='', type='unicode'):
287 def __init__(self, key='', val='', type='unicode'):
288 self.app_settings_name = key
288 self.app_settings_name = key
289 self.app_settings_type = type
289 self.app_settings_type = type
290 self.app_settings_value = val
290 self.app_settings_value = val
291
291
292 @validates('_app_settings_value')
292 @validates('_app_settings_value')
293 def validate_settings_value(self, key, val):
293 def validate_settings_value(self, key, val):
294 assert type(val) == unicode
294 assert type(val) == unicode
295 return val
295 return val
296
296
297 @hybrid_property
297 @hybrid_property
298 def app_settings_value(self):
298 def app_settings_value(self):
299 v = self._app_settings_value
299 v = self._app_settings_value
300 _type = self.app_settings_type
300 _type = self.app_settings_type
301 if _type:
301 if _type:
302 _type = self.app_settings_type.split('.')[0]
302 _type = self.app_settings_type.split('.')[0]
303 # decode the encrypted value
303 # decode the encrypted value
304 if 'encrypted' in self.app_settings_type:
304 if 'encrypted' in self.app_settings_type:
305 cipher = EncryptedTextValue()
305 cipher = EncryptedTextValue()
306 v = safe_unicode(cipher.process_result_value(v, None))
306 v = safe_unicode(cipher.process_result_value(v, None))
307
307
308 converter = self.SETTINGS_TYPES.get(_type) or \
308 converter = self.SETTINGS_TYPES.get(_type) or \
309 self.SETTINGS_TYPES['unicode']
309 self.SETTINGS_TYPES['unicode']
310 return converter(v)
310 return converter(v)
311
311
312 @app_settings_value.setter
312 @app_settings_value.setter
313 def app_settings_value(self, val):
313 def app_settings_value(self, val):
314 """
314 """
315 Setter that will always make sure we use unicode in app_settings_value
315 Setter that will always make sure we use unicode in app_settings_value
316
316
317 :param val:
317 :param val:
318 """
318 """
319 val = safe_unicode(val)
319 val = safe_unicode(val)
320 # encode the encrypted value
320 # encode the encrypted value
321 if 'encrypted' in self.app_settings_type:
321 if 'encrypted' in self.app_settings_type:
322 cipher = EncryptedTextValue()
322 cipher = EncryptedTextValue()
323 val = safe_unicode(cipher.process_bind_param(val, None))
323 val = safe_unicode(cipher.process_bind_param(val, None))
324 self._app_settings_value = val
324 self._app_settings_value = val
325
325
326 @hybrid_property
326 @hybrid_property
327 def app_settings_type(self):
327 def app_settings_type(self):
328 return self._app_settings_type
328 return self._app_settings_type
329
329
330 @app_settings_type.setter
330 @app_settings_type.setter
331 def app_settings_type(self, val):
331 def app_settings_type(self, val):
332 if val.split('.')[0] not in self.SETTINGS_TYPES:
332 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 raise Exception('type must be one of %s got %s'
333 raise Exception('type must be one of %s got %s'
334 % (self.SETTINGS_TYPES.keys(), val))
334 % (self.SETTINGS_TYPES.keys(), val))
335 self._app_settings_type = val
335 self._app_settings_type = val
336
336
337 def __unicode__(self):
337 def __unicode__(self):
338 return u"<%s('%s:%s[%s]')>" % (
338 return u"<%s('%s:%s[%s]')>" % (
339 self.__class__.__name__,
339 self.__class__.__name__,
340 self.app_settings_name, self.app_settings_value,
340 self.app_settings_name, self.app_settings_value,
341 self.app_settings_type
341 self.app_settings_type
342 )
342 )
343
343
344
344
345 class RhodeCodeUi(Base, BaseModel):
345 class RhodeCodeUi(Base, BaseModel):
346 __tablename__ = 'rhodecode_ui'
346 __tablename__ = 'rhodecode_ui'
347 __table_args__ = (
347 __table_args__ = (
348 UniqueConstraint('ui_key'),
348 UniqueConstraint('ui_key'),
349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 )
351 )
352
352
353 HOOK_REPO_SIZE = 'changegroup.repo_size'
353 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 # HG
354 # HG
355 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
355 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PULL = 'outgoing.pull_logger'
356 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
357 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PUSH = 'changegroup.push_logger'
358 HOOK_PUSH = 'changegroup.push_logger'
359
359
360 # TODO: johbo: Unify way how hooks are configured for git and hg,
360 # TODO: johbo: Unify way how hooks are configured for git and hg,
361 # git part is currently hardcoded.
361 # git part is currently hardcoded.
362
362
363 # SVN PATTERNS
363 # SVN PATTERNS
364 SVN_BRANCH_ID = 'vcs_svn_branch'
364 SVN_BRANCH_ID = 'vcs_svn_branch'
365 SVN_TAG_ID = 'vcs_svn_tag'
365 SVN_TAG_ID = 'vcs_svn_tag'
366
366
367 ui_id = Column(
367 ui_id = Column(
368 "ui_id", Integer(), nullable=False, unique=True, default=None,
368 "ui_id", Integer(), nullable=False, unique=True, default=None,
369 primary_key=True)
369 primary_key=True)
370 ui_section = Column(
370 ui_section = Column(
371 "ui_section", String(255), nullable=True, unique=None, default=None)
371 "ui_section", String(255), nullable=True, unique=None, default=None)
372 ui_key = Column(
372 ui_key = Column(
373 "ui_key", String(255), nullable=True, unique=None, default=None)
373 "ui_key", String(255), nullable=True, unique=None, default=None)
374 ui_value = Column(
374 ui_value = Column(
375 "ui_value", String(255), nullable=True, unique=None, default=None)
375 "ui_value", String(255), nullable=True, unique=None, default=None)
376 ui_active = Column(
376 ui_active = Column(
377 "ui_active", Boolean(), nullable=True, unique=None, default=True)
377 "ui_active", Boolean(), nullable=True, unique=None, default=True)
378
378
379 def __repr__(self):
379 def __repr__(self):
380 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
380 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
381 self.ui_key, self.ui_value)
381 self.ui_key, self.ui_value)
382
382
383
383
384 class RepoRhodeCodeSetting(Base, BaseModel):
384 class RepoRhodeCodeSetting(Base, BaseModel):
385 __tablename__ = 'repo_rhodecode_settings'
385 __tablename__ = 'repo_rhodecode_settings'
386 __table_args__ = (
386 __table_args__ = (
387 UniqueConstraint(
387 UniqueConstraint(
388 'app_settings_name', 'repository_id',
388 'app_settings_name', 'repository_id',
389 name='uq_repo_rhodecode_setting_name_repo_id'),
389 name='uq_repo_rhodecode_setting_name_repo_id'),
390 {'extend_existing': True, 'mysql_engine': 'InnoDB',
390 {'extend_existing': True, 'mysql_engine': 'InnoDB',
391 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
391 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
392 )
392 )
393
393
394 repository_id = Column(
394 repository_id = Column(
395 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
395 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
396 nullable=False)
396 nullable=False)
397 app_settings_id = Column(
397 app_settings_id = Column(
398 "app_settings_id", Integer(), nullable=False, unique=True,
398 "app_settings_id", Integer(), nullable=False, unique=True,
399 default=None, primary_key=True)
399 default=None, primary_key=True)
400 app_settings_name = Column(
400 app_settings_name = Column(
401 "app_settings_name", String(255), nullable=True, unique=None,
401 "app_settings_name", String(255), nullable=True, unique=None,
402 default=None)
402 default=None)
403 _app_settings_value = Column(
403 _app_settings_value = Column(
404 "app_settings_value", String(4096), nullable=True, unique=None,
404 "app_settings_value", String(4096), nullable=True, unique=None,
405 default=None)
405 default=None)
406 _app_settings_type = Column(
406 _app_settings_type = Column(
407 "app_settings_type", String(255), nullable=True, unique=None,
407 "app_settings_type", String(255), nullable=True, unique=None,
408 default=None)
408 default=None)
409
409
410 repository = relationship('Repository')
410 repository = relationship('Repository')
411
411
412 def __init__(self, repository_id, key='', val='', type='unicode'):
412 def __init__(self, repository_id, key='', val='', type='unicode'):
413 self.repository_id = repository_id
413 self.repository_id = repository_id
414 self.app_settings_name = key
414 self.app_settings_name = key
415 self.app_settings_type = type
415 self.app_settings_type = type
416 self.app_settings_value = val
416 self.app_settings_value = val
417
417
418 @validates('_app_settings_value')
418 @validates('_app_settings_value')
419 def validate_settings_value(self, key, val):
419 def validate_settings_value(self, key, val):
420 assert type(val) == unicode
420 assert type(val) == unicode
421 return val
421 return val
422
422
423 @hybrid_property
423 @hybrid_property
424 def app_settings_value(self):
424 def app_settings_value(self):
425 v = self._app_settings_value
425 v = self._app_settings_value
426 type_ = self.app_settings_type
426 type_ = self.app_settings_type
427 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
427 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
428 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
428 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
429 return converter(v)
429 return converter(v)
430
430
431 @app_settings_value.setter
431 @app_settings_value.setter
432 def app_settings_value(self, val):
432 def app_settings_value(self, val):
433 """
433 """
434 Setter that will always make sure we use unicode in app_settings_value
434 Setter that will always make sure we use unicode in app_settings_value
435
435
436 :param val:
436 :param val:
437 """
437 """
438 self._app_settings_value = safe_unicode(val)
438 self._app_settings_value = safe_unicode(val)
439
439
440 @hybrid_property
440 @hybrid_property
441 def app_settings_type(self):
441 def app_settings_type(self):
442 return self._app_settings_type
442 return self._app_settings_type
443
443
444 @app_settings_type.setter
444 @app_settings_type.setter
445 def app_settings_type(self, val):
445 def app_settings_type(self, val):
446 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
446 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
447 if val not in SETTINGS_TYPES:
447 if val not in SETTINGS_TYPES:
448 raise Exception('type must be one of %s got %s'
448 raise Exception('type must be one of %s got %s'
449 % (SETTINGS_TYPES.keys(), val))
449 % (SETTINGS_TYPES.keys(), val))
450 self._app_settings_type = val
450 self._app_settings_type = val
451
451
452 def __unicode__(self):
452 def __unicode__(self):
453 return u"<%s('%s:%s:%s[%s]')>" % (
453 return u"<%s('%s:%s:%s[%s]')>" % (
454 self.__class__.__name__, self.repository.repo_name,
454 self.__class__.__name__, self.repository.repo_name,
455 self.app_settings_name, self.app_settings_value,
455 self.app_settings_name, self.app_settings_value,
456 self.app_settings_type
456 self.app_settings_type
457 )
457 )
458
458
459
459
460 class RepoRhodeCodeUi(Base, BaseModel):
460 class RepoRhodeCodeUi(Base, BaseModel):
461 __tablename__ = 'repo_rhodecode_ui'
461 __tablename__ = 'repo_rhodecode_ui'
462 __table_args__ = (
462 __table_args__ = (
463 UniqueConstraint(
463 UniqueConstraint(
464 'repository_id', 'ui_section', 'ui_key',
464 'repository_id', 'ui_section', 'ui_key',
465 name='uq_repo_rhodecode_ui_repository_id_section_key'),
465 name='uq_repo_rhodecode_ui_repository_id_section_key'),
466 {'extend_existing': True, 'mysql_engine': 'InnoDB',
466 {'extend_existing': True, 'mysql_engine': 'InnoDB',
467 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
467 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
468 )
468 )
469
469
470 repository_id = Column(
470 repository_id = Column(
471 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
471 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
472 nullable=False)
472 nullable=False)
473 ui_id = Column(
473 ui_id = Column(
474 "ui_id", Integer(), nullable=False, unique=True, default=None,
474 "ui_id", Integer(), nullable=False, unique=True, default=None,
475 primary_key=True)
475 primary_key=True)
476 ui_section = Column(
476 ui_section = Column(
477 "ui_section", String(255), nullable=True, unique=None, default=None)
477 "ui_section", String(255), nullable=True, unique=None, default=None)
478 ui_key = Column(
478 ui_key = Column(
479 "ui_key", String(255), nullable=True, unique=None, default=None)
479 "ui_key", String(255), nullable=True, unique=None, default=None)
480 ui_value = Column(
480 ui_value = Column(
481 "ui_value", String(255), nullable=True, unique=None, default=None)
481 "ui_value", String(255), nullable=True, unique=None, default=None)
482 ui_active = Column(
482 ui_active = Column(
483 "ui_active", Boolean(), nullable=True, unique=None, default=True)
483 "ui_active", Boolean(), nullable=True, unique=None, default=True)
484
484
485 repository = relationship('Repository')
485 repository = relationship('Repository')
486
486
487 def __repr__(self):
487 def __repr__(self):
488 return '<%s[%s:%s]%s=>%s]>' % (
488 return '<%s[%s:%s]%s=>%s]>' % (
489 self.__class__.__name__, self.repository.repo_name,
489 self.__class__.__name__, self.repository.repo_name,
490 self.ui_section, self.ui_key, self.ui_value)
490 self.ui_section, self.ui_key, self.ui_value)
491
491
492
492
493 class User(Base, BaseModel):
493 class User(Base, BaseModel):
494 __tablename__ = 'users'
494 __tablename__ = 'users'
495 __table_args__ = (
495 __table_args__ = (
496 UniqueConstraint('username'), UniqueConstraint('email'),
496 UniqueConstraint('username'), UniqueConstraint('email'),
497 Index('u_username_idx', 'username'),
497 Index('u_username_idx', 'username'),
498 Index('u_email_idx', 'email'),
498 Index('u_email_idx', 'email'),
499 {'extend_existing': True, 'mysql_engine': 'InnoDB',
499 {'extend_existing': True, 'mysql_engine': 'InnoDB',
500 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
500 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
501 )
501 )
502 DEFAULT_USER = 'default'
502 DEFAULT_USER = 'default'
503 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
503 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
504 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
504 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
505
505
506 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
506 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
507 username = Column("username", String(255), nullable=True, unique=None, default=None)
507 username = Column("username", String(255), nullable=True, unique=None, default=None)
508 password = Column("password", String(255), nullable=True, unique=None, default=None)
508 password = Column("password", String(255), nullable=True, unique=None, default=None)
509 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
509 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
510 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
510 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
511 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
511 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
512 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
512 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
513 _email = Column("email", String(255), nullable=True, unique=None, default=None)
513 _email = Column("email", String(255), nullable=True, unique=None, default=None)
514 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
514 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
515 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
515 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
516 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
516 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
517 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
517 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
518 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
518 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
520 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
520 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
521
521
522 user_log = relationship('UserLog')
522 user_log = relationship('UserLog')
523 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
523 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
524
524
525 repositories = relationship('Repository')
525 repositories = relationship('Repository')
526 repository_groups = relationship('RepoGroup')
526 repository_groups = relationship('RepoGroup')
527 user_groups = relationship('UserGroup')
527 user_groups = relationship('UserGroup')
528
528
529 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
529 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
530 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
530 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
531
531
532 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
532 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
533 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
533 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
534 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
534 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
535
535
536 group_member = relationship('UserGroupMember', cascade='all')
536 group_member = relationship('UserGroupMember', cascade='all')
537
537
538 notifications = relationship('UserNotification', cascade='all')
538 notifications = relationship('UserNotification', cascade='all')
539 # notifications assigned to this user
539 # notifications assigned to this user
540 user_created_notifications = relationship('Notification', cascade='all')
540 user_created_notifications = relationship('Notification', cascade='all')
541 # comments created by this user
541 # comments created by this user
542 user_comments = relationship('ChangesetComment', cascade='all')
542 user_comments = relationship('ChangesetComment', cascade='all')
543 # user profile extra info
543 # user profile extra info
544 user_emails = relationship('UserEmailMap', cascade='all')
544 user_emails = relationship('UserEmailMap', cascade='all')
545 user_ip_map = relationship('UserIpMap', cascade='all')
545 user_ip_map = relationship('UserIpMap', cascade='all')
546 user_auth_tokens = relationship('UserApiKeys', cascade='all')
546 user_auth_tokens = relationship('UserApiKeys', cascade='all')
547 # gists
547 # gists
548 user_gists = relationship('Gist', cascade='all')
548 user_gists = relationship('Gist', cascade='all')
549 # user pull requests
549 # user pull requests
550 user_pull_requests = relationship('PullRequest', cascade='all')
550 user_pull_requests = relationship('PullRequest', cascade='all')
551 # external identities
551 # external identities
552 extenal_identities = relationship(
552 extenal_identities = relationship(
553 'ExternalIdentity',
553 'ExternalIdentity',
554 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
554 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
555 cascade='all')
555 cascade='all')
556
556
557 def __unicode__(self):
557 def __unicode__(self):
558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
558 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
559 self.user_id, self.username)
559 self.user_id, self.username)
560
560
561 @hybrid_property
561 @hybrid_property
562 def email(self):
562 def email(self):
563 return self._email
563 return self._email
564
564
565 @email.setter
565 @email.setter
566 def email(self, val):
566 def email(self, val):
567 self._email = val.lower() if val else None
567 self._email = val.lower() if val else None
568
568
569 @property
569 @property
570 def firstname(self):
570 def firstname(self):
571 # alias for future
571 # alias for future
572 return self.name
572 return self.name
573
573
574 @property
574 @property
575 def emails(self):
575 def emails(self):
576 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
576 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
577 return [self.email] + [x.email for x in other]
577 return [self.email] + [x.email for x in other]
578
578
579 @property
579 @property
580 def auth_tokens(self):
580 def auth_tokens(self):
581 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
581 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
582
582
583 @property
583 @property
584 def extra_auth_tokens(self):
584 def extra_auth_tokens(self):
585 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
585 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
586
586
587 @property
587 @property
588 def feed_token(self):
588 def feed_token(self):
589 feed_tokens = UserApiKeys.query()\
589 feed_tokens = UserApiKeys.query()\
590 .filter(UserApiKeys.user == self)\
590 .filter(UserApiKeys.user == self)\
591 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
591 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
592 .all()
592 .all()
593 if feed_tokens:
593 if feed_tokens:
594 return feed_tokens[0].api_key
594 return feed_tokens[0].api_key
595 else:
595 else:
596 # use the main token so we don't end up with nothing...
596 # use the main token so we don't end up with nothing...
597 return self.api_key
597 return self.api_key
598
598
599 @classmethod
599 @classmethod
600 def extra_valid_auth_tokens(cls, user, role=None):
600 def extra_valid_auth_tokens(cls, user, role=None):
601 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
601 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
602 .filter(or_(UserApiKeys.expires == -1,
602 .filter(or_(UserApiKeys.expires == -1,
603 UserApiKeys.expires >= time.time()))
603 UserApiKeys.expires >= time.time()))
604 if role:
604 if role:
605 tokens = tokens.filter(or_(UserApiKeys.role == role,
605 tokens = tokens.filter(or_(UserApiKeys.role == role,
606 UserApiKeys.role == UserApiKeys.ROLE_ALL))
606 UserApiKeys.role == UserApiKeys.ROLE_ALL))
607 return tokens.all()
607 return tokens.all()
608
608
609 @property
609 @property
610 def ip_addresses(self):
610 def ip_addresses(self):
611 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
611 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
612 return [x.ip_addr for x in ret]
612 return [x.ip_addr for x in ret]
613
613
614 @property
614 @property
615 def username_and_name(self):
615 def username_and_name(self):
616 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
616 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
617
617
618 @property
618 @property
619 def username_or_name_or_email(self):
619 def username_or_name_or_email(self):
620 full_name = self.full_name if self.full_name is not ' ' else None
620 full_name = self.full_name if self.full_name is not ' ' else None
621 return self.username or full_name or self.email
621 return self.username or full_name or self.email
622
622
623 @property
623 @property
624 def full_name(self):
624 def full_name(self):
625 return '%s %s' % (self.firstname, self.lastname)
625 return '%s %s' % (self.firstname, self.lastname)
626
626
627 @property
627 @property
628 def full_name_or_username(self):
628 def full_name_or_username(self):
629 return ('%s %s' % (self.firstname, self.lastname)
629 return ('%s %s' % (self.firstname, self.lastname)
630 if (self.firstname and self.lastname) else self.username)
630 if (self.firstname and self.lastname) else self.username)
631
631
632 @property
632 @property
633 def full_contact(self):
633 def full_contact(self):
634 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
634 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
635
635
636 @property
636 @property
637 def short_contact(self):
637 def short_contact(self):
638 return '%s %s' % (self.firstname, self.lastname)
638 return '%s %s' % (self.firstname, self.lastname)
639
639
640 @property
640 @property
641 def is_admin(self):
641 def is_admin(self):
642 return self.admin
642 return self.admin
643
643
644 @property
644 @property
645 def AuthUser(self):
645 def AuthUser(self):
646 """
646 """
647 Returns instance of AuthUser for this user
647 Returns instance of AuthUser for this user
648 """
648 """
649 from rhodecode.lib.auth import AuthUser
649 from rhodecode.lib.auth import AuthUser
650 return AuthUser(user_id=self.user_id, api_key=self.api_key,
650 return AuthUser(user_id=self.user_id, api_key=self.api_key,
651 username=self.username)
651 username=self.username)
652
652
653 @hybrid_property
653 @hybrid_property
654 def user_data(self):
654 def user_data(self):
655 if not self._user_data:
655 if not self._user_data:
656 return {}
656 return {}
657
657
658 try:
658 try:
659 return json.loads(self._user_data)
659 return json.loads(self._user_data)
660 except TypeError:
660 except TypeError:
661 return {}
661 return {}
662
662
663 @user_data.setter
663 @user_data.setter
664 def user_data(self, val):
664 def user_data(self, val):
665 if not isinstance(val, dict):
665 if not isinstance(val, dict):
666 raise Exception('user_data must be dict, got %s' % type(val))
666 raise Exception('user_data must be dict, got %s' % type(val))
667 try:
667 try:
668 self._user_data = json.dumps(val)
668 self._user_data = json.dumps(val)
669 except Exception:
669 except Exception:
670 log.error(traceback.format_exc())
670 log.error(traceback.format_exc())
671
671
672 @classmethod
672 @classmethod
673 def get_by_username(cls, username, case_insensitive=False,
673 def get_by_username(cls, username, case_insensitive=False,
674 cache=False, identity_cache=False):
674 cache=False, identity_cache=False):
675 session = Session()
675 session = Session()
676
676
677 if case_insensitive:
677 if case_insensitive:
678 q = cls.query().filter(
678 q = cls.query().filter(
679 func.lower(cls.username) == func.lower(username))
679 func.lower(cls.username) == func.lower(username))
680 else:
680 else:
681 q = cls.query().filter(cls.username == username)
681 q = cls.query().filter(cls.username == username)
682
682
683 if cache:
683 if cache:
684 if identity_cache:
684 if identity_cache:
685 val = cls.identity_cache(session, 'username', username)
685 val = cls.identity_cache(session, 'username', username)
686 if val:
686 if val:
687 return val
687 return val
688 else:
688 else:
689 q = q.options(
689 q = q.options(
690 FromCache("sql_cache_short",
690 FromCache("sql_cache_short",
691 "get_user_by_name_%s" % _hash_key(username)))
691 "get_user_by_name_%s" % _hash_key(username)))
692
692
693 return q.scalar()
693 return q.scalar()
694
694
695 @classmethod
695 @classmethod
696 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
696 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
697 q = cls.query().filter(cls.api_key == auth_token)
697 q = cls.query().filter(cls.api_key == auth_token)
698
698
699 if cache:
699 if cache:
700 q = q.options(FromCache("sql_cache_short",
700 q = q.options(FromCache("sql_cache_short",
701 "get_auth_token_%s" % auth_token))
701 "get_auth_token_%s" % auth_token))
702 res = q.scalar()
702 res = q.scalar()
703
703
704 if fallback and not res:
704 if fallback and not res:
705 #fallback to additional keys
705 #fallback to additional keys
706 _res = UserApiKeys.query()\
706 _res = UserApiKeys.query()\
707 .filter(UserApiKeys.api_key == auth_token)\
707 .filter(UserApiKeys.api_key == auth_token)\
708 .filter(or_(UserApiKeys.expires == -1,
708 .filter(or_(UserApiKeys.expires == -1,
709 UserApiKeys.expires >= time.time()))\
709 UserApiKeys.expires >= time.time()))\
710 .first()
710 .first()
711 if _res:
711 if _res:
712 res = _res.user
712 res = _res.user
713 return res
713 return res
714
714
715 @classmethod
715 @classmethod
716 def get_by_email(cls, email, case_insensitive=False, cache=False):
716 def get_by_email(cls, email, case_insensitive=False, cache=False):
717
717
718 if case_insensitive:
718 if case_insensitive:
719 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
719 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
720
720
721 else:
721 else:
722 q = cls.query().filter(cls.email == email)
722 q = cls.query().filter(cls.email == email)
723
723
724 if cache:
724 if cache:
725 q = q.options(FromCache("sql_cache_short",
725 q = q.options(FromCache("sql_cache_short",
726 "get_email_key_%s" % _hash_key(email)))
726 "get_email_key_%s" % _hash_key(email)))
727
727
728 ret = q.scalar()
728 ret = q.scalar()
729 if ret is None:
729 if ret is None:
730 q = UserEmailMap.query()
730 q = UserEmailMap.query()
731 # try fetching in alternate email map
731 # try fetching in alternate email map
732 if case_insensitive:
732 if case_insensitive:
733 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
733 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
734 else:
734 else:
735 q = q.filter(UserEmailMap.email == email)
735 q = q.filter(UserEmailMap.email == email)
736 q = q.options(joinedload(UserEmailMap.user))
736 q = q.options(joinedload(UserEmailMap.user))
737 if cache:
737 if cache:
738 q = q.options(FromCache("sql_cache_short",
738 q = q.options(FromCache("sql_cache_short",
739 "get_email_map_key_%s" % email))
739 "get_email_map_key_%s" % email))
740 ret = getattr(q.scalar(), 'user', None)
740 ret = getattr(q.scalar(), 'user', None)
741
741
742 return ret
742 return ret
743
743
744 @classmethod
744 @classmethod
745 def get_from_cs_author(cls, author):
745 def get_from_cs_author(cls, author):
746 """
746 """
747 Tries to get User objects out of commit author string
747 Tries to get User objects out of commit author string
748
748
749 :param author:
749 :param author:
750 """
750 """
751 from rhodecode.lib.helpers import email, author_name
751 from rhodecode.lib.helpers import email, author_name
752 # Valid email in the attribute passed, see if they're in the system
752 # Valid email in the attribute passed, see if they're in the system
753 _email = email(author)
753 _email = email(author)
754 if _email:
754 if _email:
755 user = cls.get_by_email(_email, case_insensitive=True)
755 user = cls.get_by_email(_email, case_insensitive=True)
756 if user:
756 if user:
757 return user
757 return user
758 # Maybe we can match by username?
758 # Maybe we can match by username?
759 _author = author_name(author)
759 _author = author_name(author)
760 user = cls.get_by_username(_author, case_insensitive=True)
760 user = cls.get_by_username(_author, case_insensitive=True)
761 if user:
761 if user:
762 return user
762 return user
763
763
764 def update_userdata(self, **kwargs):
764 def update_userdata(self, **kwargs):
765 usr = self
765 usr = self
766 old = usr.user_data
766 old = usr.user_data
767 old.update(**kwargs)
767 old.update(**kwargs)
768 usr.user_data = old
768 usr.user_data = old
769 Session().add(usr)
769 Session().add(usr)
770 log.debug('updated userdata with ', kwargs)
770 log.debug('updated userdata with ', kwargs)
771
771
772 def update_lastlogin(self):
772 def update_lastlogin(self):
773 """Update user lastlogin"""
773 """Update user lastlogin"""
774 self.last_login = datetime.datetime.now()
774 self.last_login = datetime.datetime.now()
775 Session().add(self)
775 Session().add(self)
776 log.debug('updated user %s lastlogin', self.username)
776 log.debug('updated user %s lastlogin', self.username)
777
777
778 def update_lastactivity(self):
778 def update_lastactivity(self):
779 """Update user lastactivity"""
779 """Update user lastactivity"""
780 usr = self
780 usr = self
781 old = usr.user_data
781 old = usr.user_data
782 old.update({'last_activity': time.time()})
782 old.update({'last_activity': time.time()})
783 usr.user_data = old
783 usr.user_data = old
784 Session().add(usr)
784 Session().add(usr)
785 log.debug('updated user %s lastactivity', usr.username)
785 log.debug('updated user %s lastactivity', usr.username)
786
786
787 def update_password(self, new_password, change_api_key=False):
787 def update_password(self, new_password, change_api_key=False):
788 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
788 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
789
789
790 self.password = get_crypt_password(new_password)
790 self.password = get_crypt_password(new_password)
791 if change_api_key:
791 if change_api_key:
792 self.api_key = generate_auth_token(self.username)
792 self.api_key = generate_auth_token(self.username)
793 Session().add(self)
793 Session().add(self)
794
794
795 @classmethod
795 @classmethod
796 def get_first_super_admin(cls):
796 def get_first_super_admin(cls):
797 user = User.query().filter(User.admin == true()).first()
797 user = User.query().filter(User.admin == true()).first()
798 if user is None:
798 if user is None:
799 raise Exception('FATAL: Missing administrative account!')
799 raise Exception('FATAL: Missing administrative account!')
800 return user
800 return user
801
801
802 @classmethod
802 @classmethod
803 def get_all_super_admins(cls):
803 def get_all_super_admins(cls):
804 """
804 """
805 Returns all admin accounts sorted by username
805 Returns all admin accounts sorted by username
806 """
806 """
807 return User.query().filter(User.admin == true())\
807 return User.query().filter(User.admin == true())\
808 .order_by(User.username.asc()).all()
808 .order_by(User.username.asc()).all()
809
809
810 @classmethod
810 @classmethod
811 def get_default_user(cls, cache=False):
811 def get_default_user(cls, cache=False):
812 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
812 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
813 if user is None:
813 if user is None:
814 raise Exception('FATAL: Missing default account!')
814 raise Exception('FATAL: Missing default account!')
815 return user
815 return user
816
816
817 def _get_default_perms(self, user, suffix=''):
817 def _get_default_perms(self, user, suffix=''):
818 from rhodecode.model.permission import PermissionModel
818 from rhodecode.model.permission import PermissionModel
819 return PermissionModel().get_default_perms(user.user_perms, suffix)
819 return PermissionModel().get_default_perms(user.user_perms, suffix)
820
820
821 def get_default_perms(self, suffix=''):
821 def get_default_perms(self, suffix=''):
822 return self._get_default_perms(self, suffix)
822 return self._get_default_perms(self, suffix)
823
823
824 def get_api_data(self, include_secrets=False, details='full'):
824 def get_api_data(self, include_secrets=False, details='full'):
825 """
825 """
826 Common function for generating user related data for API
826 Common function for generating user related data for API
827
827
828 :param include_secrets: By default secrets in the API data will be replaced
828 :param include_secrets: By default secrets in the API data will be replaced
829 by a placeholder value to prevent exposing this data by accident. In case
829 by a placeholder value to prevent exposing this data by accident. In case
830 this data shall be exposed, set this flag to ``True``.
830 this data shall be exposed, set this flag to ``True``.
831
831
832 :param details: details can be 'basic|full' basic gives only a subset of
832 :param details: details can be 'basic|full' basic gives only a subset of
833 the available user information that includes user_id, name and emails.
833 the available user information that includes user_id, name and emails.
834 """
834 """
835 user = self
835 user = self
836 user_data = self.user_data
836 user_data = self.user_data
837 data = {
837 data = {
838 'user_id': user.user_id,
838 'user_id': user.user_id,
839 'username': user.username,
839 'username': user.username,
840 'firstname': user.name,
840 'firstname': user.name,
841 'lastname': user.lastname,
841 'lastname': user.lastname,
842 'email': user.email,
842 'email': user.email,
843 'emails': user.emails,
843 'emails': user.emails,
844 }
844 }
845 if details == 'basic':
845 if details == 'basic':
846 return data
846 return data
847
847
848 api_key_length = 40
848 api_key_length = 40
849 api_key_replacement = '*' * api_key_length
849 api_key_replacement = '*' * api_key_length
850
850
851 extras = {
851 extras = {
852 'api_key': api_key_replacement,
852 'api_key': api_key_replacement,
853 'api_keys': [api_key_replacement],
853 'api_keys': [api_key_replacement],
854 'active': user.active,
854 'active': user.active,
855 'admin': user.admin,
855 'admin': user.admin,
856 'extern_type': user.extern_type,
856 'extern_type': user.extern_type,
857 'extern_name': user.extern_name,
857 'extern_name': user.extern_name,
858 'last_login': user.last_login,
858 'last_login': user.last_login,
859 'ip_addresses': user.ip_addresses,
859 'ip_addresses': user.ip_addresses,
860 'language': user_data.get('language')
860 'language': user_data.get('language')
861 }
861 }
862 data.update(extras)
862 data.update(extras)
863
863
864 if include_secrets:
864 if include_secrets:
865 data['api_key'] = user.api_key
865 data['api_key'] = user.api_key
866 data['api_keys'] = user.auth_tokens
866 data['api_keys'] = user.auth_tokens
867 return data
867 return data
868
868
869 def __json__(self):
869 def __json__(self):
870 data = {
870 data = {
871 'full_name': self.full_name,
871 'full_name': self.full_name,
872 'full_name_or_username': self.full_name_or_username,
872 'full_name_or_username': self.full_name_or_username,
873 'short_contact': self.short_contact,
873 'short_contact': self.short_contact,
874 'full_contact': self.full_contact,
874 'full_contact': self.full_contact,
875 }
875 }
876 data.update(self.get_api_data())
876 data.update(self.get_api_data())
877 return data
877 return data
878
878
879
879
880 class UserApiKeys(Base, BaseModel):
880 class UserApiKeys(Base, BaseModel):
881 __tablename__ = 'user_api_keys'
881 __tablename__ = 'user_api_keys'
882 __table_args__ = (
882 __table_args__ = (
883 Index('uak_api_key_idx', 'api_key'),
883 Index('uak_api_key_idx', 'api_key'),
884 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
884 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
885 UniqueConstraint('api_key'),
885 UniqueConstraint('api_key'),
886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
888 )
888 )
889 __mapper_args__ = {}
889 __mapper_args__ = {}
890
890
891 # ApiKey role
891 # ApiKey role
892 ROLE_ALL = 'token_role_all'
892 ROLE_ALL = 'token_role_all'
893 ROLE_HTTP = 'token_role_http'
893 ROLE_HTTP = 'token_role_http'
894 ROLE_VCS = 'token_role_vcs'
894 ROLE_VCS = 'token_role_vcs'
895 ROLE_API = 'token_role_api'
895 ROLE_API = 'token_role_api'
896 ROLE_FEED = 'token_role_feed'
896 ROLE_FEED = 'token_role_feed'
897 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
897 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
898
898
899 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
899 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
900 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
900 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
901 api_key = Column("api_key", String(255), nullable=False, unique=True)
901 api_key = Column("api_key", String(255), nullable=False, unique=True)
902 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
902 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
903 expires = Column('expires', Float(53), nullable=False)
903 expires = Column('expires', Float(53), nullable=False)
904 role = Column('role', String(255), nullable=True)
904 role = Column('role', String(255), nullable=True)
905 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
905 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
906
906
907 user = relationship('User', lazy='joined')
907 user = relationship('User', lazy='joined')
908
908
909 @classmethod
909 @classmethod
910 def _get_role_name(cls, role):
910 def _get_role_name(cls, role):
911 return {
911 return {
912 cls.ROLE_ALL: _('all'),
912 cls.ROLE_ALL: _('all'),
913 cls.ROLE_HTTP: _('http/web interface'),
913 cls.ROLE_HTTP: _('http/web interface'),
914 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
914 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
915 cls.ROLE_API: _('api calls'),
915 cls.ROLE_API: _('api calls'),
916 cls.ROLE_FEED: _('feed access'),
916 cls.ROLE_FEED: _('feed access'),
917 }.get(role, role)
917 }.get(role, role)
918
918
919 @property
919 @property
920 def expired(self):
920 def expired(self):
921 if self.expires == -1:
921 if self.expires == -1:
922 return False
922 return False
923 return time.time() > self.expires
923 return time.time() > self.expires
924
924
925 @property
925 @property
926 def role_humanized(self):
926 def role_humanized(self):
927 return self._get_role_name(self.role)
927 return self._get_role_name(self.role)
928
928
929
929
930 class UserEmailMap(Base, BaseModel):
930 class UserEmailMap(Base, BaseModel):
931 __tablename__ = 'user_email_map'
931 __tablename__ = 'user_email_map'
932 __table_args__ = (
932 __table_args__ = (
933 Index('uem_email_idx', 'email'),
933 Index('uem_email_idx', 'email'),
934 UniqueConstraint('email'),
934 UniqueConstraint('email'),
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
937 )
937 )
938 __mapper_args__ = {}
938 __mapper_args__ = {}
939
939
940 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
940 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
942 _email = Column("email", String(255), nullable=True, unique=False, default=None)
942 _email = Column("email", String(255), nullable=True, unique=False, default=None)
943 user = relationship('User', lazy='joined')
943 user = relationship('User', lazy='joined')
944
944
945 @validates('_email')
945 @validates('_email')
946 def validate_email(self, key, email):
946 def validate_email(self, key, email):
947 # check if this email is not main one
947 # check if this email is not main one
948 main_email = Session().query(User).filter(User.email == email).scalar()
948 main_email = Session().query(User).filter(User.email == email).scalar()
949 if main_email is not None:
949 if main_email is not None:
950 raise AttributeError('email %s is present is user table' % email)
950 raise AttributeError('email %s is present is user table' % email)
951 return email
951 return email
952
952
953 @hybrid_property
953 @hybrid_property
954 def email(self):
954 def email(self):
955 return self._email
955 return self._email
956
956
957 @email.setter
957 @email.setter
958 def email(self, val):
958 def email(self, val):
959 self._email = val.lower() if val else None
959 self._email = val.lower() if val else None
960
960
961
961
962 class UserIpMap(Base, BaseModel):
962 class UserIpMap(Base, BaseModel):
963 __tablename__ = 'user_ip_map'
963 __tablename__ = 'user_ip_map'
964 __table_args__ = (
964 __table_args__ = (
965 UniqueConstraint('user_id', 'ip_addr'),
965 UniqueConstraint('user_id', 'ip_addr'),
966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
968 )
968 )
969 __mapper_args__ = {}
969 __mapper_args__ = {}
970
970
971 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
971 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
972 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
972 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
973 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
973 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
974 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
974 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
975 description = Column("description", String(10000), nullable=True, unique=None, default=None)
975 description = Column("description", String(10000), nullable=True, unique=None, default=None)
976 user = relationship('User', lazy='joined')
976 user = relationship('User', lazy='joined')
977
977
978 @classmethod
978 @classmethod
979 def _get_ip_range(cls, ip_addr):
979 def _get_ip_range(cls, ip_addr):
980 net = ipaddress.ip_network(ip_addr, strict=False)
980 net = ipaddress.ip_network(ip_addr, strict=False)
981 return [str(net.network_address), str(net.broadcast_address)]
981 return [str(net.network_address), str(net.broadcast_address)]
982
982
983 def __json__(self):
983 def __json__(self):
984 return {
984 return {
985 'ip_addr': self.ip_addr,
985 'ip_addr': self.ip_addr,
986 'ip_range': self._get_ip_range(self.ip_addr),
986 'ip_range': self._get_ip_range(self.ip_addr),
987 }
987 }
988
988
989 def __unicode__(self):
989 def __unicode__(self):
990 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
990 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
991 self.user_id, self.ip_addr)
991 self.user_id, self.ip_addr)
992
992
993 class UserLog(Base, BaseModel):
993 class UserLog(Base, BaseModel):
994 __tablename__ = 'user_logs'
994 __tablename__ = 'user_logs'
995 __table_args__ = (
995 __table_args__ = (
996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
998 )
998 )
999 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
999 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1000 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1000 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1001 username = Column("username", String(255), nullable=True, unique=None, default=None)
1001 username = Column("username", String(255), nullable=True, unique=None, default=None)
1002 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1002 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1003 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1003 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1004 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1004 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1005 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1005 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1006 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1006 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1007
1007
1008 def __unicode__(self):
1008 def __unicode__(self):
1009 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1009 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1010 self.repository_name,
1010 self.repository_name,
1011 self.action)
1011 self.action)
1012
1012
1013 @property
1013 @property
1014 def action_as_day(self):
1014 def action_as_day(self):
1015 return datetime.date(*self.action_date.timetuple()[:3])
1015 return datetime.date(*self.action_date.timetuple()[:3])
1016
1016
1017 user = relationship('User')
1017 user = relationship('User')
1018 repository = relationship('Repository', cascade='')
1018 repository = relationship('Repository', cascade='')
1019
1019
1020
1020
1021 class UserGroup(Base, BaseModel):
1021 class UserGroup(Base, BaseModel):
1022 __tablename__ = 'users_groups'
1022 __tablename__ = 'users_groups'
1023 __table_args__ = (
1023 __table_args__ = (
1024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1026 )
1026 )
1027
1027
1028 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1028 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1029 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1029 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1030 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1030 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1031 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1031 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1032 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1032 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1033 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1033 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1034 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1034 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1035 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1035 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1036
1036
1037 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1037 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1038 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1038 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1039 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1039 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1040 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1040 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1041 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1041 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1042 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1042 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1043
1043
1044 user = relationship('User')
1044 user = relationship('User')
1045
1045
1046 @hybrid_property
1046 @hybrid_property
1047 def group_data(self):
1047 def group_data(self):
1048 if not self._group_data:
1048 if not self._group_data:
1049 return {}
1049 return {}
1050
1050
1051 try:
1051 try:
1052 return json.loads(self._group_data)
1052 return json.loads(self._group_data)
1053 except TypeError:
1053 except TypeError:
1054 return {}
1054 return {}
1055
1055
1056 @group_data.setter
1056 @group_data.setter
1057 def group_data(self, val):
1057 def group_data(self, val):
1058 try:
1058 try:
1059 self._group_data = json.dumps(val)
1059 self._group_data = json.dumps(val)
1060 except Exception:
1060 except Exception:
1061 log.error(traceback.format_exc())
1061 log.error(traceback.format_exc())
1062
1062
1063 def __unicode__(self):
1063 def __unicode__(self):
1064 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1064 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1065 self.users_group_id,
1065 self.users_group_id,
1066 self.users_group_name)
1066 self.users_group_name)
1067
1067
1068 @classmethod
1068 @classmethod
1069 def get_by_group_name(cls, group_name, cache=False,
1069 def get_by_group_name(cls, group_name, cache=False,
1070 case_insensitive=False):
1070 case_insensitive=False):
1071 if case_insensitive:
1071 if case_insensitive:
1072 q = cls.query().filter(func.lower(cls.users_group_name) ==
1072 q = cls.query().filter(func.lower(cls.users_group_name) ==
1073 func.lower(group_name))
1073 func.lower(group_name))
1074
1074
1075 else:
1075 else:
1076 q = cls.query().filter(cls.users_group_name == group_name)
1076 q = cls.query().filter(cls.users_group_name == group_name)
1077 if cache:
1077 if cache:
1078 q = q.options(FromCache(
1078 q = q.options(FromCache(
1079 "sql_cache_short",
1079 "sql_cache_short",
1080 "get_group_%s" % _hash_key(group_name)))
1080 "get_group_%s" % _hash_key(group_name)))
1081 return q.scalar()
1081 return q.scalar()
1082
1082
1083 @classmethod
1083 @classmethod
1084 def get(cls, user_group_id, cache=False):
1084 def get(cls, user_group_id, cache=False):
1085 user_group = cls.query()
1085 user_group = cls.query()
1086 if cache:
1086 if cache:
1087 user_group = user_group.options(FromCache("sql_cache_short",
1087 user_group = user_group.options(FromCache("sql_cache_short",
1088 "get_users_group_%s" % user_group_id))
1088 "get_users_group_%s" % user_group_id))
1089 return user_group.get(user_group_id)
1089 return user_group.get(user_group_id)
1090
1090
1091 def permissions(self, with_admins=True, with_owner=True):
1091 def permissions(self, with_admins=True, with_owner=True):
1092 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1092 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1093 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1093 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1094 joinedload(UserUserGroupToPerm.user),
1094 joinedload(UserUserGroupToPerm.user),
1095 joinedload(UserUserGroupToPerm.permission),)
1095 joinedload(UserUserGroupToPerm.permission),)
1096
1096
1097 # get owners and admins and permissions. We do a trick of re-writing
1097 # get owners and admins and permissions. We do a trick of re-writing
1098 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1098 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1099 # has a global reference and changing one object propagates to all
1099 # has a global reference and changing one object propagates to all
1100 # others. This means if admin is also an owner admin_row that change
1100 # others. This means if admin is also an owner admin_row that change
1101 # would propagate to both objects
1101 # would propagate to both objects
1102 perm_rows = []
1102 perm_rows = []
1103 for _usr in q.all():
1103 for _usr in q.all():
1104 usr = AttributeDict(_usr.user.get_dict())
1104 usr = AttributeDict(_usr.user.get_dict())
1105 usr.permission = _usr.permission.permission_name
1105 usr.permission = _usr.permission.permission_name
1106 perm_rows.append(usr)
1106 perm_rows.append(usr)
1107
1107
1108 # filter the perm rows by 'default' first and then sort them by
1108 # filter the perm rows by 'default' first and then sort them by
1109 # admin,write,read,none permissions sorted again alphabetically in
1109 # admin,write,read,none permissions sorted again alphabetically in
1110 # each group
1110 # each group
1111 perm_rows = sorted(perm_rows, key=display_sort)
1111 perm_rows = sorted(perm_rows, key=display_sort)
1112
1112
1113 _admin_perm = 'usergroup.admin'
1113 _admin_perm = 'usergroup.admin'
1114 owner_row = []
1114 owner_row = []
1115 if with_owner:
1115 if with_owner:
1116 usr = AttributeDict(self.user.get_dict())
1116 usr = AttributeDict(self.user.get_dict())
1117 usr.owner_row = True
1117 usr.owner_row = True
1118 usr.permission = _admin_perm
1118 usr.permission = _admin_perm
1119 owner_row.append(usr)
1119 owner_row.append(usr)
1120
1120
1121 super_admin_rows = []
1121 super_admin_rows = []
1122 if with_admins:
1122 if with_admins:
1123 for usr in User.get_all_super_admins():
1123 for usr in User.get_all_super_admins():
1124 # if this admin is also owner, don't double the record
1124 # if this admin is also owner, don't double the record
1125 if usr.user_id == owner_row[0].user_id:
1125 if usr.user_id == owner_row[0].user_id:
1126 owner_row[0].admin_row = True
1126 owner_row[0].admin_row = True
1127 else:
1127 else:
1128 usr = AttributeDict(usr.get_dict())
1128 usr = AttributeDict(usr.get_dict())
1129 usr.admin_row = True
1129 usr.admin_row = True
1130 usr.permission = _admin_perm
1130 usr.permission = _admin_perm
1131 super_admin_rows.append(usr)
1131 super_admin_rows.append(usr)
1132
1132
1133 return super_admin_rows + owner_row + perm_rows
1133 return super_admin_rows + owner_row + perm_rows
1134
1134
1135 def permission_user_groups(self):
1135 def permission_user_groups(self):
1136 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1136 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1137 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1137 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1138 joinedload(UserGroupUserGroupToPerm.target_user_group),
1138 joinedload(UserGroupUserGroupToPerm.target_user_group),
1139 joinedload(UserGroupUserGroupToPerm.permission),)
1139 joinedload(UserGroupUserGroupToPerm.permission),)
1140
1140
1141 perm_rows = []
1141 perm_rows = []
1142 for _user_group in q.all():
1142 for _user_group in q.all():
1143 usr = AttributeDict(_user_group.user_group.get_dict())
1143 usr = AttributeDict(_user_group.user_group.get_dict())
1144 usr.permission = _user_group.permission.permission_name
1144 usr.permission = _user_group.permission.permission_name
1145 perm_rows.append(usr)
1145 perm_rows.append(usr)
1146
1146
1147 return perm_rows
1147 return perm_rows
1148
1148
1149 def _get_default_perms(self, user_group, suffix=''):
1149 def _get_default_perms(self, user_group, suffix=''):
1150 from rhodecode.model.permission import PermissionModel
1150 from rhodecode.model.permission import PermissionModel
1151 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1151 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1152
1152
1153 def get_default_perms(self, suffix=''):
1153 def get_default_perms(self, suffix=''):
1154 return self._get_default_perms(self, suffix)
1154 return self._get_default_perms(self, suffix)
1155
1155
1156 def get_api_data(self, with_group_members=True, include_secrets=False):
1156 def get_api_data(self, with_group_members=True, include_secrets=False):
1157 """
1157 """
1158 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1158 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1159 basically forwarded.
1159 basically forwarded.
1160
1160
1161 """
1161 """
1162 user_group = self
1162 user_group = self
1163
1163
1164 data = {
1164 data = {
1165 'users_group_id': user_group.users_group_id,
1165 'users_group_id': user_group.users_group_id,
1166 'group_name': user_group.users_group_name,
1166 'group_name': user_group.users_group_name,
1167 'group_description': user_group.user_group_description,
1167 'group_description': user_group.user_group_description,
1168 'active': user_group.users_group_active,
1168 'active': user_group.users_group_active,
1169 'owner': user_group.user.username,
1169 'owner': user_group.user.username,
1170 }
1170 }
1171 if with_group_members:
1171 if with_group_members:
1172 users = []
1172 users = []
1173 for user in user_group.members:
1173 for user in user_group.members:
1174 user = user.user
1174 user = user.user
1175 users.append(user.get_api_data(include_secrets=include_secrets))
1175 users.append(user.get_api_data(include_secrets=include_secrets))
1176 data['users'] = users
1176 data['users'] = users
1177
1177
1178 return data
1178 return data
1179
1179
1180
1180
1181 class UserGroupMember(Base, BaseModel):
1181 class UserGroupMember(Base, BaseModel):
1182 __tablename__ = 'users_groups_members'
1182 __tablename__ = 'users_groups_members'
1183 __table_args__ = (
1183 __table_args__ = (
1184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1186 )
1186 )
1187
1187
1188 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1188 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1189 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1189 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1190 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1190 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1191
1191
1192 user = relationship('User', lazy='joined')
1192 user = relationship('User', lazy='joined')
1193 users_group = relationship('UserGroup')
1193 users_group = relationship('UserGroup')
1194
1194
1195 def __init__(self, gr_id='', u_id=''):
1195 def __init__(self, gr_id='', u_id=''):
1196 self.users_group_id = gr_id
1196 self.users_group_id = gr_id
1197 self.user_id = u_id
1197 self.user_id = u_id
1198
1198
1199
1199
1200 class RepositoryField(Base, BaseModel):
1200 class RepositoryField(Base, BaseModel):
1201 __tablename__ = 'repositories_fields'
1201 __tablename__ = 'repositories_fields'
1202 __table_args__ = (
1202 __table_args__ = (
1203 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1203 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1206 )
1206 )
1207 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1207 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1208
1208
1209 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1209 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1210 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1210 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1211 field_key = Column("field_key", String(250))
1211 field_key = Column("field_key", String(250))
1212 field_label = Column("field_label", String(1024), nullable=False)
1212 field_label = Column("field_label", String(1024), nullable=False)
1213 field_value = Column("field_value", String(10000), nullable=False)
1213 field_value = Column("field_value", String(10000), nullable=False)
1214 field_desc = Column("field_desc", String(1024), nullable=False)
1214 field_desc = Column("field_desc", String(1024), nullable=False)
1215 field_type = Column("field_type", String(255), nullable=False, unique=None)
1215 field_type = Column("field_type", String(255), nullable=False, unique=None)
1216 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1216 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1217
1217
1218 repository = relationship('Repository')
1218 repository = relationship('Repository')
1219
1219
1220 @property
1220 @property
1221 def field_key_prefixed(self):
1221 def field_key_prefixed(self):
1222 return 'ex_%s' % self.field_key
1222 return 'ex_%s' % self.field_key
1223
1223
1224 @classmethod
1224 @classmethod
1225 def un_prefix_key(cls, key):
1225 def un_prefix_key(cls, key):
1226 if key.startswith(cls.PREFIX):
1226 if key.startswith(cls.PREFIX):
1227 return key[len(cls.PREFIX):]
1227 return key[len(cls.PREFIX):]
1228 return key
1228 return key
1229
1229
1230 @classmethod
1230 @classmethod
1231 def get_by_key_name(cls, key, repo):
1231 def get_by_key_name(cls, key, repo):
1232 row = cls.query()\
1232 row = cls.query()\
1233 .filter(cls.repository == repo)\
1233 .filter(cls.repository == repo)\
1234 .filter(cls.field_key == key).scalar()
1234 .filter(cls.field_key == key).scalar()
1235 return row
1235 return row
1236
1236
1237
1237
1238 class Repository(Base, BaseModel):
1238 class Repository(Base, BaseModel):
1239 __tablename__ = 'repositories'
1239 __tablename__ = 'repositories'
1240 __table_args__ = (
1240 __table_args__ = (
1241 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1241 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1243 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1243 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1244 )
1244 )
1245 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1245 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1246 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1246 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1247
1247
1248 STATE_CREATED = 'repo_state_created'
1248 STATE_CREATED = 'repo_state_created'
1249 STATE_PENDING = 'repo_state_pending'
1249 STATE_PENDING = 'repo_state_pending'
1250 STATE_ERROR = 'repo_state_error'
1250 STATE_ERROR = 'repo_state_error'
1251
1251
1252 LOCK_AUTOMATIC = 'lock_auto'
1252 LOCK_AUTOMATIC = 'lock_auto'
1253 LOCK_API = 'lock_api'
1253 LOCK_API = 'lock_api'
1254 LOCK_WEB = 'lock_web'
1254 LOCK_WEB = 'lock_web'
1255 LOCK_PULL = 'lock_pull'
1255 LOCK_PULL = 'lock_pull'
1256
1256
1257 NAME_SEP = URL_SEP
1257 NAME_SEP = URL_SEP
1258
1258
1259 repo_id = Column(
1259 repo_id = Column(
1260 "repo_id", Integer(), nullable=False, unique=True, default=None,
1260 "repo_id", Integer(), nullable=False, unique=True, default=None,
1261 primary_key=True)
1261 primary_key=True)
1262 _repo_name = Column(
1262 _repo_name = Column(
1263 "repo_name", Text(), nullable=False, default=None)
1263 "repo_name", Text(), nullable=False, default=None)
1264 _repo_name_hash = Column(
1264 _repo_name_hash = Column(
1265 "repo_name_hash", String(255), nullable=False, unique=True)
1265 "repo_name_hash", String(255), nullable=False, unique=True)
1266 repo_state = Column("repo_state", String(255), nullable=True)
1266 repo_state = Column("repo_state", String(255), nullable=True)
1267
1267
1268 clone_uri = Column(
1268 clone_uri = Column(
1269 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1269 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1270 default=None)
1270 default=None)
1271 repo_type = Column(
1271 repo_type = Column(
1272 "repo_type", String(255), nullable=False, unique=False, default=None)
1272 "repo_type", String(255), nullable=False, unique=False, default=None)
1273 user_id = Column(
1273 user_id = Column(
1274 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1274 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1275 unique=False, default=None)
1275 unique=False, default=None)
1276 private = Column(
1276 private = Column(
1277 "private", Boolean(), nullable=True, unique=None, default=None)
1277 "private", Boolean(), nullable=True, unique=None, default=None)
1278 enable_statistics = Column(
1278 enable_statistics = Column(
1279 "statistics", Boolean(), nullable=True, unique=None, default=True)
1279 "statistics", Boolean(), nullable=True, unique=None, default=True)
1280 enable_downloads = Column(
1280 enable_downloads = Column(
1281 "downloads", Boolean(), nullable=True, unique=None, default=True)
1281 "downloads", Boolean(), nullable=True, unique=None, default=True)
1282 description = Column(
1282 description = Column(
1283 "description", String(10000), nullable=True, unique=None, default=None)
1283 "description", String(10000), nullable=True, unique=None, default=None)
1284 created_on = Column(
1284 created_on = Column(
1285 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1285 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1286 default=datetime.datetime.now)
1286 default=datetime.datetime.now)
1287 updated_on = Column(
1287 updated_on = Column(
1288 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1288 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1289 default=datetime.datetime.now)
1289 default=datetime.datetime.now)
1290 _landing_revision = Column(
1290 _landing_revision = Column(
1291 "landing_revision", String(255), nullable=False, unique=False,
1291 "landing_revision", String(255), nullable=False, unique=False,
1292 default=None)
1292 default=None)
1293 enable_locking = Column(
1293 enable_locking = Column(
1294 "enable_locking", Boolean(), nullable=False, unique=None,
1294 "enable_locking", Boolean(), nullable=False, unique=None,
1295 default=False)
1295 default=False)
1296 _locked = Column(
1296 _locked = Column(
1297 "locked", String(255), nullable=True, unique=False, default=None)
1297 "locked", String(255), nullable=True, unique=False, default=None)
1298 _changeset_cache = Column(
1298 _changeset_cache = Column(
1299 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1299 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1300
1300
1301 fork_id = Column(
1301 fork_id = Column(
1302 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1302 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1303 nullable=True, unique=False, default=None)
1303 nullable=True, unique=False, default=None)
1304 group_id = Column(
1304 group_id = Column(
1305 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1305 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1306 unique=False, default=None)
1306 unique=False, default=None)
1307
1307
1308 user = relationship('User', lazy='joined')
1308 user = relationship('User', lazy='joined')
1309 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1309 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1310 group = relationship('RepoGroup', lazy='joined')
1310 group = relationship('RepoGroup', lazy='joined')
1311 repo_to_perm = relationship(
1311 repo_to_perm = relationship(
1312 'UserRepoToPerm', cascade='all',
1312 'UserRepoToPerm', cascade='all',
1313 order_by='UserRepoToPerm.repo_to_perm_id')
1313 order_by='UserRepoToPerm.repo_to_perm_id')
1314 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1314 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1315 stats = relationship('Statistics', cascade='all', uselist=False)
1315 stats = relationship('Statistics', cascade='all', uselist=False)
1316
1316
1317 followers = relationship(
1317 followers = relationship(
1318 'UserFollowing',
1318 'UserFollowing',
1319 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1319 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1320 cascade='all')
1320 cascade='all')
1321 extra_fields = relationship(
1321 extra_fields = relationship(
1322 'RepositoryField', cascade="all, delete, delete-orphan")
1322 'RepositoryField', cascade="all, delete, delete-orphan")
1323 logs = relationship('UserLog')
1323 logs = relationship('UserLog')
1324 comments = relationship(
1324 comments = relationship(
1325 'ChangesetComment', cascade="all, delete, delete-orphan")
1325 'ChangesetComment', cascade="all, delete, delete-orphan")
1326 pull_requests_source = relationship(
1326 pull_requests_source = relationship(
1327 'PullRequest',
1327 'PullRequest',
1328 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1328 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1329 cascade="all, delete, delete-orphan")
1329 cascade="all, delete, delete-orphan")
1330 pull_requests_target = relationship(
1330 pull_requests_target = relationship(
1331 'PullRequest',
1331 'PullRequest',
1332 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1332 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1333 cascade="all, delete, delete-orphan")
1333 cascade="all, delete, delete-orphan")
1334 ui = relationship('RepoRhodeCodeUi', cascade="all")
1334 ui = relationship('RepoRhodeCodeUi', cascade="all")
1335 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1335 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1336 integrations = relationship('Integration',
1336 integrations = relationship('Integration',
1337 cascade="all, delete, delete-orphan")
1337 cascade="all, delete, delete-orphan")
1338
1338
1339 def __unicode__(self):
1339 def __unicode__(self):
1340 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1340 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1341 safe_unicode(self.repo_name))
1341 safe_unicode(self.repo_name))
1342
1342
1343 @hybrid_property
1343 @hybrid_property
1344 def landing_rev(self):
1344 def landing_rev(self):
1345 # always should return [rev_type, rev]
1345 # always should return [rev_type, rev]
1346 if self._landing_revision:
1346 if self._landing_revision:
1347 _rev_info = self._landing_revision.split(':')
1347 _rev_info = self._landing_revision.split(':')
1348 if len(_rev_info) < 2:
1348 if len(_rev_info) < 2:
1349 _rev_info.insert(0, 'rev')
1349 _rev_info.insert(0, 'rev')
1350 return [_rev_info[0], _rev_info[1]]
1350 return [_rev_info[0], _rev_info[1]]
1351 return [None, None]
1351 return [None, None]
1352
1352
1353 @landing_rev.setter
1353 @landing_rev.setter
1354 def landing_rev(self, val):
1354 def landing_rev(self, val):
1355 if ':' not in val:
1355 if ':' not in val:
1356 raise ValueError('value must be delimited with `:` and consist '
1356 raise ValueError('value must be delimited with `:` and consist '
1357 'of <rev_type>:<rev>, got %s instead' % val)
1357 'of <rev_type>:<rev>, got %s instead' % val)
1358 self._landing_revision = val
1358 self._landing_revision = val
1359
1359
1360 @hybrid_property
1360 @hybrid_property
1361 def locked(self):
1361 def locked(self):
1362 if self._locked:
1362 if self._locked:
1363 user_id, timelocked, reason = self._locked.split(':')
1363 user_id, timelocked, reason = self._locked.split(':')
1364 lock_values = int(user_id), timelocked, reason
1364 lock_values = int(user_id), timelocked, reason
1365 else:
1365 else:
1366 lock_values = [None, None, None]
1366 lock_values = [None, None, None]
1367 return lock_values
1367 return lock_values
1368
1368
1369 @locked.setter
1369 @locked.setter
1370 def locked(self, val):
1370 def locked(self, val):
1371 if val and isinstance(val, (list, tuple)):
1371 if val and isinstance(val, (list, tuple)):
1372 self._locked = ':'.join(map(str, val))
1372 self._locked = ':'.join(map(str, val))
1373 else:
1373 else:
1374 self._locked = None
1374 self._locked = None
1375
1375
1376 @hybrid_property
1376 @hybrid_property
1377 def changeset_cache(self):
1377 def changeset_cache(self):
1378 from rhodecode.lib.vcs.backends.base import EmptyCommit
1378 from rhodecode.lib.vcs.backends.base import EmptyCommit
1379 dummy = EmptyCommit().__json__()
1379 dummy = EmptyCommit().__json__()
1380 if not self._changeset_cache:
1380 if not self._changeset_cache:
1381 return dummy
1381 return dummy
1382 try:
1382 try:
1383 return json.loads(self._changeset_cache)
1383 return json.loads(self._changeset_cache)
1384 except TypeError:
1384 except TypeError:
1385 return dummy
1385 return dummy
1386 except Exception:
1386 except Exception:
1387 log.error(traceback.format_exc())
1387 log.error(traceback.format_exc())
1388 return dummy
1388 return dummy
1389
1389
1390 @changeset_cache.setter
1390 @changeset_cache.setter
1391 def changeset_cache(self, val):
1391 def changeset_cache(self, val):
1392 try:
1392 try:
1393 self._changeset_cache = json.dumps(val)
1393 self._changeset_cache = json.dumps(val)
1394 except Exception:
1394 except Exception:
1395 log.error(traceback.format_exc())
1395 log.error(traceback.format_exc())
1396
1396
1397 @hybrid_property
1397 @hybrid_property
1398 def repo_name(self):
1398 def repo_name(self):
1399 return self._repo_name
1399 return self._repo_name
1400
1400
1401 @repo_name.setter
1401 @repo_name.setter
1402 def repo_name(self, value):
1402 def repo_name(self, value):
1403 self._repo_name = value
1403 self._repo_name = value
1404 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1404 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1405
1405
1406 @classmethod
1406 @classmethod
1407 def normalize_repo_name(cls, repo_name):
1407 def normalize_repo_name(cls, repo_name):
1408 """
1408 """
1409 Normalizes os specific repo_name to the format internally stored inside
1409 Normalizes os specific repo_name to the format internally stored inside
1410 database using URL_SEP
1410 database using URL_SEP
1411
1411
1412 :param cls:
1412 :param cls:
1413 :param repo_name:
1413 :param repo_name:
1414 """
1414 """
1415 return cls.NAME_SEP.join(repo_name.split(os.sep))
1415 return cls.NAME_SEP.join(repo_name.split(os.sep))
1416
1416
1417 @classmethod
1417 @classmethod
1418 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1418 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1419 session = Session()
1419 session = Session()
1420 q = session.query(cls).filter(cls.repo_name == repo_name)
1420 q = session.query(cls).filter(cls.repo_name == repo_name)
1421
1421
1422 if cache:
1422 if cache:
1423 if identity_cache:
1423 if identity_cache:
1424 val = cls.identity_cache(session, 'repo_name', repo_name)
1424 val = cls.identity_cache(session, 'repo_name', repo_name)
1425 if val:
1425 if val:
1426 return val
1426 return val
1427 else:
1427 else:
1428 q = q.options(
1428 q = q.options(
1429 FromCache("sql_cache_short",
1429 FromCache("sql_cache_short",
1430 "get_repo_by_name_%s" % _hash_key(repo_name)))
1430 "get_repo_by_name_%s" % _hash_key(repo_name)))
1431
1431
1432 return q.scalar()
1432 return q.scalar()
1433
1433
1434 @classmethod
1434 @classmethod
1435 def get_by_full_path(cls, repo_full_path):
1435 def get_by_full_path(cls, repo_full_path):
1436 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1436 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1437 repo_name = cls.normalize_repo_name(repo_name)
1437 repo_name = cls.normalize_repo_name(repo_name)
1438 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1438 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1439
1439
1440 @classmethod
1440 @classmethod
1441 def get_repo_forks(cls, repo_id):
1441 def get_repo_forks(cls, repo_id):
1442 return cls.query().filter(Repository.fork_id == repo_id)
1442 return cls.query().filter(Repository.fork_id == repo_id)
1443
1443
1444 @classmethod
1444 @classmethod
1445 def base_path(cls):
1445 def base_path(cls):
1446 """
1446 """
1447 Returns base path when all repos are stored
1447 Returns base path when all repos are stored
1448
1448
1449 :param cls:
1449 :param cls:
1450 """
1450 """
1451 q = Session().query(RhodeCodeUi)\
1451 q = Session().query(RhodeCodeUi)\
1452 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1452 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1453 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1453 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1454 return q.one().ui_value
1454 return q.one().ui_value
1455
1455
1456 @classmethod
1456 @classmethod
1457 def is_valid(cls, repo_name):
1457 def is_valid(cls, repo_name):
1458 """
1458 """
1459 returns True if given repo name is a valid filesystem repository
1459 returns True if given repo name is a valid filesystem repository
1460
1460
1461 :param cls:
1461 :param cls:
1462 :param repo_name:
1462 :param repo_name:
1463 """
1463 """
1464 from rhodecode.lib.utils import is_valid_repo
1464 from rhodecode.lib.utils import is_valid_repo
1465
1465
1466 return is_valid_repo(repo_name, cls.base_path())
1466 return is_valid_repo(repo_name, cls.base_path())
1467
1467
1468 @classmethod
1468 @classmethod
1469 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1469 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1470 case_insensitive=True):
1470 case_insensitive=True):
1471 q = Repository.query()
1471 q = Repository.query()
1472
1472
1473 if not isinstance(user_id, Optional):
1473 if not isinstance(user_id, Optional):
1474 q = q.filter(Repository.user_id == user_id)
1474 q = q.filter(Repository.user_id == user_id)
1475
1475
1476 if not isinstance(group_id, Optional):
1476 if not isinstance(group_id, Optional):
1477 q = q.filter(Repository.group_id == group_id)
1477 q = q.filter(Repository.group_id == group_id)
1478
1478
1479 if case_insensitive:
1479 if case_insensitive:
1480 q = q.order_by(func.lower(Repository.repo_name))
1480 q = q.order_by(func.lower(Repository.repo_name))
1481 else:
1481 else:
1482 q = q.order_by(Repository.repo_name)
1482 q = q.order_by(Repository.repo_name)
1483 return q.all()
1483 return q.all()
1484
1484
1485 @property
1485 @property
1486 def forks(self):
1486 def forks(self):
1487 """
1487 """
1488 Return forks of this repo
1488 Return forks of this repo
1489 """
1489 """
1490 return Repository.get_repo_forks(self.repo_id)
1490 return Repository.get_repo_forks(self.repo_id)
1491
1491
1492 @property
1492 @property
1493 def parent(self):
1493 def parent(self):
1494 """
1494 """
1495 Returns fork parent
1495 Returns fork parent
1496 """
1496 """
1497 return self.fork
1497 return self.fork
1498
1498
1499 @property
1499 @property
1500 def just_name(self):
1500 def just_name(self):
1501 return self.repo_name.split(self.NAME_SEP)[-1]
1501 return self.repo_name.split(self.NAME_SEP)[-1]
1502
1502
1503 @property
1503 @property
1504 def groups_with_parents(self):
1504 def groups_with_parents(self):
1505 groups = []
1505 groups = []
1506 if self.group is None:
1506 if self.group is None:
1507 return groups
1507 return groups
1508
1508
1509 cur_gr = self.group
1509 cur_gr = self.group
1510 groups.insert(0, cur_gr)
1510 groups.insert(0, cur_gr)
1511 while 1:
1511 while 1:
1512 gr = getattr(cur_gr, 'parent_group', None)
1512 gr = getattr(cur_gr, 'parent_group', None)
1513 cur_gr = cur_gr.parent_group
1513 cur_gr = cur_gr.parent_group
1514 if gr is None:
1514 if gr is None:
1515 break
1515 break
1516 groups.insert(0, gr)
1516 groups.insert(0, gr)
1517
1517
1518 return groups
1518 return groups
1519
1519
1520 @property
1520 @property
1521 def groups_and_repo(self):
1521 def groups_and_repo(self):
1522 return self.groups_with_parents, self
1522 return self.groups_with_parents, self
1523
1523
1524 @LazyProperty
1524 @LazyProperty
1525 def repo_path(self):
1525 def repo_path(self):
1526 """
1526 """
1527 Returns base full path for that repository means where it actually
1527 Returns base full path for that repository means where it actually
1528 exists on a filesystem
1528 exists on a filesystem
1529 """
1529 """
1530 q = Session().query(RhodeCodeUi).filter(
1530 q = Session().query(RhodeCodeUi).filter(
1531 RhodeCodeUi.ui_key == self.NAME_SEP)
1531 RhodeCodeUi.ui_key == self.NAME_SEP)
1532 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1532 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1533 return q.one().ui_value
1533 return q.one().ui_value
1534
1534
1535 @property
1535 @property
1536 def repo_full_path(self):
1536 def repo_full_path(self):
1537 p = [self.repo_path]
1537 p = [self.repo_path]
1538 # we need to split the name by / since this is how we store the
1538 # we need to split the name by / since this is how we store the
1539 # names in the database, but that eventually needs to be converted
1539 # names in the database, but that eventually needs to be converted
1540 # into a valid system path
1540 # into a valid system path
1541 p += self.repo_name.split(self.NAME_SEP)
1541 p += self.repo_name.split(self.NAME_SEP)
1542 return os.path.join(*map(safe_unicode, p))
1542 return os.path.join(*map(safe_unicode, p))
1543
1543
1544 @property
1544 @property
1545 def cache_keys(self):
1545 def cache_keys(self):
1546 """
1546 """
1547 Returns associated cache keys for that repo
1547 Returns associated cache keys for that repo
1548 """
1548 """
1549 return CacheKey.query()\
1549 return CacheKey.query()\
1550 .filter(CacheKey.cache_args == self.repo_name)\
1550 .filter(CacheKey.cache_args == self.repo_name)\
1551 .order_by(CacheKey.cache_key)\
1551 .order_by(CacheKey.cache_key)\
1552 .all()
1552 .all()
1553
1553
1554 def get_new_name(self, repo_name):
1554 def get_new_name(self, repo_name):
1555 """
1555 """
1556 returns new full repository name based on assigned group and new new
1556 returns new full repository name based on assigned group and new new
1557
1557
1558 :param group_name:
1558 :param group_name:
1559 """
1559 """
1560 path_prefix = self.group.full_path_splitted if self.group else []
1560 path_prefix = self.group.full_path_splitted if self.group else []
1561 return self.NAME_SEP.join(path_prefix + [repo_name])
1561 return self.NAME_SEP.join(path_prefix + [repo_name])
1562
1562
1563 @property
1563 @property
1564 def _config(self):
1564 def _config(self):
1565 """
1565 """
1566 Returns db based config object.
1566 Returns db based config object.
1567 """
1567 """
1568 from rhodecode.lib.utils import make_db_config
1568 from rhodecode.lib.utils import make_db_config
1569 return make_db_config(clear_session=False, repo=self)
1569 return make_db_config(clear_session=False, repo=self)
1570
1570
1571 def permissions(self, with_admins=True, with_owner=True):
1571 def permissions(self, with_admins=True, with_owner=True):
1572 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1572 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1573 q = q.options(joinedload(UserRepoToPerm.repository),
1573 q = q.options(joinedload(UserRepoToPerm.repository),
1574 joinedload(UserRepoToPerm.user),
1574 joinedload(UserRepoToPerm.user),
1575 joinedload(UserRepoToPerm.permission),)
1575 joinedload(UserRepoToPerm.permission),)
1576
1576
1577 # get owners and admins and permissions. We do a trick of re-writing
1577 # get owners and admins and permissions. We do a trick of re-writing
1578 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1578 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1579 # has a global reference and changing one object propagates to all
1579 # has a global reference and changing one object propagates to all
1580 # others. This means if admin is also an owner admin_row that change
1580 # others. This means if admin is also an owner admin_row that change
1581 # would propagate to both objects
1581 # would propagate to both objects
1582 perm_rows = []
1582 perm_rows = []
1583 for _usr in q.all():
1583 for _usr in q.all():
1584 usr = AttributeDict(_usr.user.get_dict())
1584 usr = AttributeDict(_usr.user.get_dict())
1585 usr.permission = _usr.permission.permission_name
1585 usr.permission = _usr.permission.permission_name
1586 perm_rows.append(usr)
1586 perm_rows.append(usr)
1587
1587
1588 # filter the perm rows by 'default' first and then sort them by
1588 # filter the perm rows by 'default' first and then sort them by
1589 # admin,write,read,none permissions sorted again alphabetically in
1589 # admin,write,read,none permissions sorted again alphabetically in
1590 # each group
1590 # each group
1591 perm_rows = sorted(perm_rows, key=display_sort)
1591 perm_rows = sorted(perm_rows, key=display_sort)
1592
1592
1593 _admin_perm = 'repository.admin'
1593 _admin_perm = 'repository.admin'
1594 owner_row = []
1594 owner_row = []
1595 if with_owner:
1595 if with_owner:
1596 usr = AttributeDict(self.user.get_dict())
1596 usr = AttributeDict(self.user.get_dict())
1597 usr.owner_row = True
1597 usr.owner_row = True
1598 usr.permission = _admin_perm
1598 usr.permission = _admin_perm
1599 owner_row.append(usr)
1599 owner_row.append(usr)
1600
1600
1601 super_admin_rows = []
1601 super_admin_rows = []
1602 if with_admins:
1602 if with_admins:
1603 for usr in User.get_all_super_admins():
1603 for usr in User.get_all_super_admins():
1604 # if this admin is also owner, don't double the record
1604 # if this admin is also owner, don't double the record
1605 if usr.user_id == owner_row[0].user_id:
1605 if usr.user_id == owner_row[0].user_id:
1606 owner_row[0].admin_row = True
1606 owner_row[0].admin_row = True
1607 else:
1607 else:
1608 usr = AttributeDict(usr.get_dict())
1608 usr = AttributeDict(usr.get_dict())
1609 usr.admin_row = True
1609 usr.admin_row = True
1610 usr.permission = _admin_perm
1610 usr.permission = _admin_perm
1611 super_admin_rows.append(usr)
1611 super_admin_rows.append(usr)
1612
1612
1613 return super_admin_rows + owner_row + perm_rows
1613 return super_admin_rows + owner_row + perm_rows
1614
1614
1615 def permission_user_groups(self):
1615 def permission_user_groups(self):
1616 q = UserGroupRepoToPerm.query().filter(
1616 q = UserGroupRepoToPerm.query().filter(
1617 UserGroupRepoToPerm.repository == self)
1617 UserGroupRepoToPerm.repository == self)
1618 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1618 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1619 joinedload(UserGroupRepoToPerm.users_group),
1619 joinedload(UserGroupRepoToPerm.users_group),
1620 joinedload(UserGroupRepoToPerm.permission),)
1620 joinedload(UserGroupRepoToPerm.permission),)
1621
1621
1622 perm_rows = []
1622 perm_rows = []
1623 for _user_group in q.all():
1623 for _user_group in q.all():
1624 usr = AttributeDict(_user_group.users_group.get_dict())
1624 usr = AttributeDict(_user_group.users_group.get_dict())
1625 usr.permission = _user_group.permission.permission_name
1625 usr.permission = _user_group.permission.permission_name
1626 perm_rows.append(usr)
1626 perm_rows.append(usr)
1627
1627
1628 return perm_rows
1628 return perm_rows
1629
1629
1630 def get_api_data(self, include_secrets=False):
1630 def get_api_data(self, include_secrets=False):
1631 """
1631 """
1632 Common function for generating repo api data
1632 Common function for generating repo api data
1633
1633
1634 :param include_secrets: See :meth:`User.get_api_data`.
1634 :param include_secrets: See :meth:`User.get_api_data`.
1635
1635
1636 """
1636 """
1637 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1637 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1638 # move this methods on models level.
1638 # move this methods on models level.
1639 from rhodecode.model.settings import SettingsModel
1639 from rhodecode.model.settings import SettingsModel
1640
1640
1641 repo = self
1641 repo = self
1642 _user_id, _time, _reason = self.locked
1642 _user_id, _time, _reason = self.locked
1643
1643
1644 data = {
1644 data = {
1645 'repo_id': repo.repo_id,
1645 'repo_id': repo.repo_id,
1646 'repo_name': repo.repo_name,
1646 'repo_name': repo.repo_name,
1647 'repo_type': repo.repo_type,
1647 'repo_type': repo.repo_type,
1648 'clone_uri': repo.clone_uri or '',
1648 'clone_uri': repo.clone_uri or '',
1649 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1649 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1650 'private': repo.private,
1650 'private': repo.private,
1651 'created_on': repo.created_on,
1651 'created_on': repo.created_on,
1652 'description': repo.description,
1652 'description': repo.description,
1653 'landing_rev': repo.landing_rev,
1653 'landing_rev': repo.landing_rev,
1654 'owner': repo.user.username,
1654 'owner': repo.user.username,
1655 'fork_of': repo.fork.repo_name if repo.fork else None,
1655 'fork_of': repo.fork.repo_name if repo.fork else None,
1656 'enable_statistics': repo.enable_statistics,
1656 'enable_statistics': repo.enable_statistics,
1657 'enable_locking': repo.enable_locking,
1657 'enable_locking': repo.enable_locking,
1658 'enable_downloads': repo.enable_downloads,
1658 'enable_downloads': repo.enable_downloads,
1659 'last_changeset': repo.changeset_cache,
1659 'last_changeset': repo.changeset_cache,
1660 'locked_by': User.get(_user_id).get_api_data(
1660 'locked_by': User.get(_user_id).get_api_data(
1661 include_secrets=include_secrets) if _user_id else None,
1661 include_secrets=include_secrets) if _user_id else None,
1662 'locked_date': time_to_datetime(_time) if _time else None,
1662 'locked_date': time_to_datetime(_time) if _time else None,
1663 'lock_reason': _reason if _reason else None,
1663 'lock_reason': _reason if _reason else None,
1664 }
1664 }
1665
1665
1666 # TODO: mikhail: should be per-repo settings here
1666 # TODO: mikhail: should be per-repo settings here
1667 rc_config = SettingsModel().get_all_settings()
1667 rc_config = SettingsModel().get_all_settings()
1668 repository_fields = str2bool(
1668 repository_fields = str2bool(
1669 rc_config.get('rhodecode_repository_fields'))
1669 rc_config.get('rhodecode_repository_fields'))
1670 if repository_fields:
1670 if repository_fields:
1671 for f in self.extra_fields:
1671 for f in self.extra_fields:
1672 data[f.field_key_prefixed] = f.field_value
1672 data[f.field_key_prefixed] = f.field_value
1673
1673
1674 return data
1674 return data
1675
1675
1676 @classmethod
1676 @classmethod
1677 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1677 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1678 if not lock_time:
1678 if not lock_time:
1679 lock_time = time.time()
1679 lock_time = time.time()
1680 if not lock_reason:
1680 if not lock_reason:
1681 lock_reason = cls.LOCK_AUTOMATIC
1681 lock_reason = cls.LOCK_AUTOMATIC
1682 repo.locked = [user_id, lock_time, lock_reason]
1682 repo.locked = [user_id, lock_time, lock_reason]
1683 Session().add(repo)
1683 Session().add(repo)
1684 Session().commit()
1684 Session().commit()
1685
1685
1686 @classmethod
1686 @classmethod
1687 def unlock(cls, repo):
1687 def unlock(cls, repo):
1688 repo.locked = None
1688 repo.locked = None
1689 Session().add(repo)
1689 Session().add(repo)
1690 Session().commit()
1690 Session().commit()
1691
1691
1692 @classmethod
1692 @classmethod
1693 def getlock(cls, repo):
1693 def getlock(cls, repo):
1694 return repo.locked
1694 return repo.locked
1695
1695
1696 def is_user_lock(self, user_id):
1696 def is_user_lock(self, user_id):
1697 if self.lock[0]:
1697 if self.lock[0]:
1698 lock_user_id = safe_int(self.lock[0])
1698 lock_user_id = safe_int(self.lock[0])
1699 user_id = safe_int(user_id)
1699 user_id = safe_int(user_id)
1700 # both are ints, and they are equal
1700 # both are ints, and they are equal
1701 return all([lock_user_id, user_id]) and lock_user_id == user_id
1701 return all([lock_user_id, user_id]) and lock_user_id == user_id
1702
1702
1703 return False
1703 return False
1704
1704
1705 def get_locking_state(self, action, user_id, only_when_enabled=True):
1705 def get_locking_state(self, action, user_id, only_when_enabled=True):
1706 """
1706 """
1707 Checks locking on this repository, if locking is enabled and lock is
1707 Checks locking on this repository, if locking is enabled and lock is
1708 present returns a tuple of make_lock, locked, locked_by.
1708 present returns a tuple of make_lock, locked, locked_by.
1709 make_lock can have 3 states None (do nothing) True, make lock
1709 make_lock can have 3 states None (do nothing) True, make lock
1710 False release lock, This value is later propagated to hooks, which
1710 False release lock, This value is later propagated to hooks, which
1711 do the locking. Think about this as signals passed to hooks what to do.
1711 do the locking. Think about this as signals passed to hooks what to do.
1712
1712
1713 """
1713 """
1714 # TODO: johbo: This is part of the business logic and should be moved
1714 # TODO: johbo: This is part of the business logic and should be moved
1715 # into the RepositoryModel.
1715 # into the RepositoryModel.
1716
1716
1717 if action not in ('push', 'pull'):
1717 if action not in ('push', 'pull'):
1718 raise ValueError("Invalid action value: %s" % repr(action))
1718 raise ValueError("Invalid action value: %s" % repr(action))
1719
1719
1720 # defines if locked error should be thrown to user
1720 # defines if locked error should be thrown to user
1721 currently_locked = False
1721 currently_locked = False
1722 # defines if new lock should be made, tri-state
1722 # defines if new lock should be made, tri-state
1723 make_lock = None
1723 make_lock = None
1724 repo = self
1724 repo = self
1725 user = User.get(user_id)
1725 user = User.get(user_id)
1726
1726
1727 lock_info = repo.locked
1727 lock_info = repo.locked
1728
1728
1729 if repo and (repo.enable_locking or not only_when_enabled):
1729 if repo and (repo.enable_locking or not only_when_enabled):
1730 if action == 'push':
1730 if action == 'push':
1731 # check if it's already locked !, if it is compare users
1731 # check if it's already locked !, if it is compare users
1732 locked_by_user_id = lock_info[0]
1732 locked_by_user_id = lock_info[0]
1733 if user.user_id == locked_by_user_id:
1733 if user.user_id == locked_by_user_id:
1734 log.debug(
1734 log.debug(
1735 'Got `push` action from user %s, now unlocking', user)
1735 'Got `push` action from user %s, now unlocking', user)
1736 # unlock if we have push from user who locked
1736 # unlock if we have push from user who locked
1737 make_lock = False
1737 make_lock = False
1738 else:
1738 else:
1739 # we're not the same user who locked, ban with
1739 # we're not the same user who locked, ban with
1740 # code defined in settings (default is 423 HTTP Locked) !
1740 # code defined in settings (default is 423 HTTP Locked) !
1741 log.debug('Repo %s is currently locked by %s', repo, user)
1741 log.debug('Repo %s is currently locked by %s', repo, user)
1742 currently_locked = True
1742 currently_locked = True
1743 elif action == 'pull':
1743 elif action == 'pull':
1744 # [0] user [1] date
1744 # [0] user [1] date
1745 if lock_info[0] and lock_info[1]:
1745 if lock_info[0] and lock_info[1]:
1746 log.debug('Repo %s is currently locked by %s', repo, user)
1746 log.debug('Repo %s is currently locked by %s', repo, user)
1747 currently_locked = True
1747 currently_locked = True
1748 else:
1748 else:
1749 log.debug('Setting lock on repo %s by %s', repo, user)
1749 log.debug('Setting lock on repo %s by %s', repo, user)
1750 make_lock = True
1750 make_lock = True
1751
1751
1752 else:
1752 else:
1753 log.debug('Repository %s do not have locking enabled', repo)
1753 log.debug('Repository %s do not have locking enabled', repo)
1754
1754
1755 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1755 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1756 make_lock, currently_locked, lock_info)
1756 make_lock, currently_locked, lock_info)
1757
1757
1758 from rhodecode.lib.auth import HasRepoPermissionAny
1758 from rhodecode.lib.auth import HasRepoPermissionAny
1759 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1759 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1760 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1760 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1761 # if we don't have at least write permission we cannot make a lock
1761 # if we don't have at least write permission we cannot make a lock
1762 log.debug('lock state reset back to FALSE due to lack '
1762 log.debug('lock state reset back to FALSE due to lack '
1763 'of at least read permission')
1763 'of at least read permission')
1764 make_lock = False
1764 make_lock = False
1765
1765
1766 return make_lock, currently_locked, lock_info
1766 return make_lock, currently_locked, lock_info
1767
1767
1768 @property
1768 @property
1769 def last_db_change(self):
1769 def last_db_change(self):
1770 return self.updated_on
1770 return self.updated_on
1771
1771
1772 @property
1772 @property
1773 def clone_uri_hidden(self):
1773 def clone_uri_hidden(self):
1774 clone_uri = self.clone_uri
1774 clone_uri = self.clone_uri
1775 if clone_uri:
1775 if clone_uri:
1776 import urlobject
1776 import urlobject
1777 url_obj = urlobject.URLObject(clone_uri)
1777 url_obj = urlobject.URLObject(clone_uri)
1778 if url_obj.password:
1778 if url_obj.password:
1779 clone_uri = url_obj.with_password('*****')
1779 clone_uri = url_obj.with_password('*****')
1780 return clone_uri
1780 return clone_uri
1781
1781
1782 def clone_url(self, **override):
1782 def clone_url(self, **override):
1783 qualified_home_url = url('home', qualified=True)
1783 qualified_home_url = url('home', qualified=True)
1784
1784
1785 uri_tmpl = None
1785 uri_tmpl = None
1786 if 'with_id' in override:
1786 if 'with_id' in override:
1787 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1787 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1788 del override['with_id']
1788 del override['with_id']
1789
1789
1790 if 'uri_tmpl' in override:
1790 if 'uri_tmpl' in override:
1791 uri_tmpl = override['uri_tmpl']
1791 uri_tmpl = override['uri_tmpl']
1792 del override['uri_tmpl']
1792 del override['uri_tmpl']
1793
1793
1794 # we didn't override our tmpl from **overrides
1794 # we didn't override our tmpl from **overrides
1795 if not uri_tmpl:
1795 if not uri_tmpl:
1796 uri_tmpl = self.DEFAULT_CLONE_URI
1796 uri_tmpl = self.DEFAULT_CLONE_URI
1797 try:
1797 try:
1798 from pylons import tmpl_context as c
1798 from pylons import tmpl_context as c
1799 uri_tmpl = c.clone_uri_tmpl
1799 uri_tmpl = c.clone_uri_tmpl
1800 except Exception:
1800 except Exception:
1801 # in any case if we call this outside of request context,
1801 # in any case if we call this outside of request context,
1802 # ie, not having tmpl_context set up
1802 # ie, not having tmpl_context set up
1803 pass
1803 pass
1804
1804
1805 return get_clone_url(uri_tmpl=uri_tmpl,
1805 return get_clone_url(uri_tmpl=uri_tmpl,
1806 qualifed_home_url=qualified_home_url,
1806 qualifed_home_url=qualified_home_url,
1807 repo_name=self.repo_name,
1807 repo_name=self.repo_name,
1808 repo_id=self.repo_id, **override)
1808 repo_id=self.repo_id, **override)
1809
1809
1810 def set_state(self, state):
1810 def set_state(self, state):
1811 self.repo_state = state
1811 self.repo_state = state
1812 Session().add(self)
1812 Session().add(self)
1813 #==========================================================================
1813 #==========================================================================
1814 # SCM PROPERTIES
1814 # SCM PROPERTIES
1815 #==========================================================================
1815 #==========================================================================
1816
1816
1817 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1817 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1818 return get_commit_safe(
1818 return get_commit_safe(
1819 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1819 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1820
1820
1821 def get_changeset(self, rev=None, pre_load=None):
1821 def get_changeset(self, rev=None, pre_load=None):
1822 warnings.warn("Use get_commit", DeprecationWarning)
1822 warnings.warn("Use get_commit", DeprecationWarning)
1823 commit_id = None
1823 commit_id = None
1824 commit_idx = None
1824 commit_idx = None
1825 if isinstance(rev, basestring):
1825 if isinstance(rev, basestring):
1826 commit_id = rev
1826 commit_id = rev
1827 else:
1827 else:
1828 commit_idx = rev
1828 commit_idx = rev
1829 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1829 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1830 pre_load=pre_load)
1830 pre_load=pre_load)
1831
1831
1832 def get_landing_commit(self):
1832 def get_landing_commit(self):
1833 """
1833 """
1834 Returns landing commit, or if that doesn't exist returns the tip
1834 Returns landing commit, or if that doesn't exist returns the tip
1835 """
1835 """
1836 _rev_type, _rev = self.landing_rev
1836 _rev_type, _rev = self.landing_rev
1837 commit = self.get_commit(_rev)
1837 commit = self.get_commit(_rev)
1838 if isinstance(commit, EmptyCommit):
1838 if isinstance(commit, EmptyCommit):
1839 return self.get_commit()
1839 return self.get_commit()
1840 return commit
1840 return commit
1841
1841
1842 def update_commit_cache(self, cs_cache=None, config=None):
1842 def update_commit_cache(self, cs_cache=None, config=None):
1843 """
1843 """
1844 Update cache of last changeset for repository, keys should be::
1844 Update cache of last changeset for repository, keys should be::
1845
1845
1846 short_id
1846 short_id
1847 raw_id
1847 raw_id
1848 revision
1848 revision
1849 parents
1849 parents
1850 message
1850 message
1851 date
1851 date
1852 author
1852 author
1853
1853
1854 :param cs_cache:
1854 :param cs_cache:
1855 """
1855 """
1856 from rhodecode.lib.vcs.backends.base import BaseChangeset
1856 from rhodecode.lib.vcs.backends.base import BaseChangeset
1857 if cs_cache is None:
1857 if cs_cache is None:
1858 # use no-cache version here
1858 # use no-cache version here
1859 scm_repo = self.scm_instance(cache=False, config=config)
1859 scm_repo = self.scm_instance(cache=False, config=config)
1860 if scm_repo:
1860 if scm_repo:
1861 cs_cache = scm_repo.get_commit(
1861 cs_cache = scm_repo.get_commit(
1862 pre_load=["author", "date", "message", "parents"])
1862 pre_load=["author", "date", "message", "parents"])
1863 else:
1863 else:
1864 cs_cache = EmptyCommit()
1864 cs_cache = EmptyCommit()
1865
1865
1866 if isinstance(cs_cache, BaseChangeset):
1866 if isinstance(cs_cache, BaseChangeset):
1867 cs_cache = cs_cache.__json__()
1867 cs_cache = cs_cache.__json__()
1868
1868
1869 def is_outdated(new_cs_cache):
1869 def is_outdated(new_cs_cache):
1870 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1870 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1871 new_cs_cache['revision'] != self.changeset_cache['revision']):
1871 new_cs_cache['revision'] != self.changeset_cache['revision']):
1872 return True
1872 return True
1873 return False
1873 return False
1874
1874
1875 # check if we have maybe already latest cached revision
1875 # check if we have maybe already latest cached revision
1876 if is_outdated(cs_cache) or not self.changeset_cache:
1876 if is_outdated(cs_cache) or not self.changeset_cache:
1877 _default = datetime.datetime.fromtimestamp(0)
1877 _default = datetime.datetime.fromtimestamp(0)
1878 last_change = cs_cache.get('date') or _default
1878 last_change = cs_cache.get('date') or _default
1879 log.debug('updated repo %s with new cs cache %s',
1879 log.debug('updated repo %s with new cs cache %s',
1880 self.repo_name, cs_cache)
1880 self.repo_name, cs_cache)
1881 self.updated_on = last_change
1881 self.updated_on = last_change
1882 self.changeset_cache = cs_cache
1882 self.changeset_cache = cs_cache
1883 Session().add(self)
1883 Session().add(self)
1884 Session().commit()
1884 Session().commit()
1885 else:
1885 else:
1886 log.debug('Skipping update_commit_cache for repo:`%s` '
1886 log.debug('Skipping update_commit_cache for repo:`%s` '
1887 'commit already with latest changes', self.repo_name)
1887 'commit already with latest changes', self.repo_name)
1888
1888
1889 @property
1889 @property
1890 def tip(self):
1890 def tip(self):
1891 return self.get_commit('tip')
1891 return self.get_commit('tip')
1892
1892
1893 @property
1893 @property
1894 def author(self):
1894 def author(self):
1895 return self.tip.author
1895 return self.tip.author
1896
1896
1897 @property
1897 @property
1898 def last_change(self):
1898 def last_change(self):
1899 return self.scm_instance().last_change
1899 return self.scm_instance().last_change
1900
1900
1901 def get_comments(self, revisions=None):
1901 def get_comments(self, revisions=None):
1902 """
1902 """
1903 Returns comments for this repository grouped by revisions
1903 Returns comments for this repository grouped by revisions
1904
1904
1905 :param revisions: filter query by revisions only
1905 :param revisions: filter query by revisions only
1906 """
1906 """
1907 cmts = ChangesetComment.query()\
1907 cmts = ChangesetComment.query()\
1908 .filter(ChangesetComment.repo == self)
1908 .filter(ChangesetComment.repo == self)
1909 if revisions:
1909 if revisions:
1910 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1910 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1911 grouped = collections.defaultdict(list)
1911 grouped = collections.defaultdict(list)
1912 for cmt in cmts.all():
1912 for cmt in cmts.all():
1913 grouped[cmt.revision].append(cmt)
1913 grouped[cmt.revision].append(cmt)
1914 return grouped
1914 return grouped
1915
1915
1916 def statuses(self, revisions=None):
1916 def statuses(self, revisions=None):
1917 """
1917 """
1918 Returns statuses for this repository
1918 Returns statuses for this repository
1919
1919
1920 :param revisions: list of revisions to get statuses for
1920 :param revisions: list of revisions to get statuses for
1921 """
1921 """
1922 statuses = ChangesetStatus.query()\
1922 statuses = ChangesetStatus.query()\
1923 .filter(ChangesetStatus.repo == self)\
1923 .filter(ChangesetStatus.repo == self)\
1924 .filter(ChangesetStatus.version == 0)
1924 .filter(ChangesetStatus.version == 0)
1925
1925
1926 if revisions:
1926 if revisions:
1927 # Try doing the filtering in chunks to avoid hitting limits
1927 # Try doing the filtering in chunks to avoid hitting limits
1928 size = 500
1928 size = 500
1929 status_results = []
1929 status_results = []
1930 for chunk in xrange(0, len(revisions), size):
1930 for chunk in xrange(0, len(revisions), size):
1931 status_results += statuses.filter(
1931 status_results += statuses.filter(
1932 ChangesetStatus.revision.in_(
1932 ChangesetStatus.revision.in_(
1933 revisions[chunk: chunk+size])
1933 revisions[chunk: chunk+size])
1934 ).all()
1934 ).all()
1935 else:
1935 else:
1936 status_results = statuses.all()
1936 status_results = statuses.all()
1937
1937
1938 grouped = {}
1938 grouped = {}
1939
1939
1940 # maybe we have open new pullrequest without a status?
1940 # maybe we have open new pullrequest without a status?
1941 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1941 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1942 status_lbl = ChangesetStatus.get_status_lbl(stat)
1942 status_lbl = ChangesetStatus.get_status_lbl(stat)
1943 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1943 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1944 for rev in pr.revisions:
1944 for rev in pr.revisions:
1945 pr_id = pr.pull_request_id
1945 pr_id = pr.pull_request_id
1946 pr_repo = pr.target_repo.repo_name
1946 pr_repo = pr.target_repo.repo_name
1947 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1947 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1948
1948
1949 for stat in status_results:
1949 for stat in status_results:
1950 pr_id = pr_repo = None
1950 pr_id = pr_repo = None
1951 if stat.pull_request:
1951 if stat.pull_request:
1952 pr_id = stat.pull_request.pull_request_id
1952 pr_id = stat.pull_request.pull_request_id
1953 pr_repo = stat.pull_request.target_repo.repo_name
1953 pr_repo = stat.pull_request.target_repo.repo_name
1954 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1954 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1955 pr_id, pr_repo]
1955 pr_id, pr_repo]
1956 return grouped
1956 return grouped
1957
1957
1958 # ==========================================================================
1958 # ==========================================================================
1959 # SCM CACHE INSTANCE
1959 # SCM CACHE INSTANCE
1960 # ==========================================================================
1960 # ==========================================================================
1961
1961
1962 def scm_instance(self, **kwargs):
1962 def scm_instance(self, **kwargs):
1963 import rhodecode
1963 import rhodecode
1964
1964
1965 # Passing a config will not hit the cache currently only used
1965 # Passing a config will not hit the cache currently only used
1966 # for repo2dbmapper
1966 # for repo2dbmapper
1967 config = kwargs.pop('config', None)
1967 config = kwargs.pop('config', None)
1968 cache = kwargs.pop('cache', None)
1968 cache = kwargs.pop('cache', None)
1969 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1969 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1970 # if cache is NOT defined use default global, else we have a full
1970 # if cache is NOT defined use default global, else we have a full
1971 # control over cache behaviour
1971 # control over cache behaviour
1972 if cache is None and full_cache and not config:
1972 if cache is None and full_cache and not config:
1973 return self._get_instance_cached()
1973 return self._get_instance_cached()
1974 return self._get_instance(cache=bool(cache), config=config)
1974 return self._get_instance(cache=bool(cache), config=config)
1975
1975
1976 def _get_instance_cached(self):
1976 def _get_instance_cached(self):
1977 @cache_region('long_term')
1977 @cache_region('long_term')
1978 def _get_repo(cache_key):
1978 def _get_repo(cache_key):
1979 return self._get_instance()
1979 return self._get_instance()
1980
1980
1981 invalidator_context = CacheKey.repo_context_cache(
1981 invalidator_context = CacheKey.repo_context_cache(
1982 _get_repo, self.repo_name, None, thread_scoped=True)
1982 _get_repo, self.repo_name, None, thread_scoped=True)
1983
1983
1984 with invalidator_context as context:
1984 with invalidator_context as context:
1985 context.invalidate()
1985 context.invalidate()
1986 repo = context.compute()
1986 repo = context.compute()
1987
1987
1988 return repo
1988 return repo
1989
1989
1990 def _get_instance(self, cache=True, config=None):
1990 def _get_instance(self, cache=True, config=None):
1991 config = config or self._config
1991 config = config or self._config
1992 custom_wire = {
1992 custom_wire = {
1993 'cache': cache # controls the vcs.remote cache
1993 'cache': cache # controls the vcs.remote cache
1994 }
1994 }
1995
1995
1996 repo = get_vcs_instance(
1996 repo = get_vcs_instance(
1997 repo_path=safe_str(self.repo_full_path),
1997 repo_path=safe_str(self.repo_full_path),
1998 config=config,
1998 config=config,
1999 with_wire=custom_wire,
1999 with_wire=custom_wire,
2000 create=False)
2000 create=False)
2001
2001
2002 return repo
2002 return repo
2003
2003
2004 def __json__(self):
2004 def __json__(self):
2005 return {'landing_rev': self.landing_rev}
2005 return {'landing_rev': self.landing_rev}
2006
2006
2007 def get_dict(self):
2007 def get_dict(self):
2008
2008
2009 # Since we transformed `repo_name` to a hybrid property, we need to
2009 # Since we transformed `repo_name` to a hybrid property, we need to
2010 # keep compatibility with the code which uses `repo_name` field.
2010 # keep compatibility with the code which uses `repo_name` field.
2011
2011
2012 result = super(Repository, self).get_dict()
2012 result = super(Repository, self).get_dict()
2013 result['repo_name'] = result.pop('_repo_name', None)
2013 result['repo_name'] = result.pop('_repo_name', None)
2014 return result
2014 return result
2015
2015
2016
2016
2017 class RepoGroup(Base, BaseModel):
2017 class RepoGroup(Base, BaseModel):
2018 __tablename__ = 'groups'
2018 __tablename__ = 'groups'
2019 __table_args__ = (
2019 __table_args__ = (
2020 UniqueConstraint('group_name', 'group_parent_id'),
2020 UniqueConstraint('group_name', 'group_parent_id'),
2021 CheckConstraint('group_id != group_parent_id'),
2021 CheckConstraint('group_id != group_parent_id'),
2022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2024 )
2024 )
2025 __mapper_args__ = {'order_by': 'group_name'}
2025 __mapper_args__ = {'order_by': 'group_name'}
2026
2026
2027 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2027 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2028
2028
2029 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2029 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2030 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2030 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2031 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2031 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2032 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2032 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2033 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2033 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2035 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2035 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2036
2036
2037 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2037 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2038 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2038 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2039 parent_group = relationship('RepoGroup', remote_side=group_id)
2039 parent_group = relationship('RepoGroup', remote_side=group_id)
2040 user = relationship('User')
2040 user = relationship('User')
2041 integrations = relationship('Integration',
2041 integrations = relationship('Integration',
2042 cascade="all, delete, delete-orphan")
2042 cascade="all, delete, delete-orphan")
2043
2043
2044 def __init__(self, group_name='', parent_group=None):
2044 def __init__(self, group_name='', parent_group=None):
2045 self.group_name = group_name
2045 self.group_name = group_name
2046 self.parent_group = parent_group
2046 self.parent_group = parent_group
2047
2047
2048 def __unicode__(self):
2048 def __unicode__(self):
2049 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2049 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2050 self.group_name)
2050 self.group_name)
2051
2051
2052 @classmethod
2052 @classmethod
2053 def _generate_choice(cls, repo_group):
2053 def _generate_choice(cls, repo_group):
2054 from webhelpers.html import literal as _literal
2054 from webhelpers.html import literal as _literal
2055 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2055 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2056 return repo_group.group_id, _name(repo_group.full_path_splitted)
2056 return repo_group.group_id, _name(repo_group.full_path_splitted)
2057
2057
2058 @classmethod
2058 @classmethod
2059 def groups_choices(cls, groups=None, show_empty_group=True):
2059 def groups_choices(cls, groups=None, show_empty_group=True):
2060 if not groups:
2060 if not groups:
2061 groups = cls.query().all()
2061 groups = cls.query().all()
2062
2062
2063 repo_groups = []
2063 repo_groups = []
2064 if show_empty_group:
2064 if show_empty_group:
2065 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2065 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2066
2066
2067 repo_groups.extend([cls._generate_choice(x) for x in groups])
2067 repo_groups.extend([cls._generate_choice(x) for x in groups])
2068
2068
2069 repo_groups = sorted(
2069 repo_groups = sorted(
2070 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2070 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2071 return repo_groups
2071 return repo_groups
2072
2072
2073 @classmethod
2073 @classmethod
2074 def url_sep(cls):
2074 def url_sep(cls):
2075 return URL_SEP
2075 return URL_SEP
2076
2076
2077 @classmethod
2077 @classmethod
2078 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2078 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2079 if case_insensitive:
2079 if case_insensitive:
2080 gr = cls.query().filter(func.lower(cls.group_name)
2080 gr = cls.query().filter(func.lower(cls.group_name)
2081 == func.lower(group_name))
2081 == func.lower(group_name))
2082 else:
2082 else:
2083 gr = cls.query().filter(cls.group_name == group_name)
2083 gr = cls.query().filter(cls.group_name == group_name)
2084 if cache:
2084 if cache:
2085 gr = gr.options(FromCache(
2085 gr = gr.options(FromCache(
2086 "sql_cache_short",
2086 "sql_cache_short",
2087 "get_group_%s" % _hash_key(group_name)))
2087 "get_group_%s" % _hash_key(group_name)))
2088 return gr.scalar()
2088 return gr.scalar()
2089
2089
2090 @classmethod
2090 @classmethod
2091 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2091 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2092 case_insensitive=True):
2092 case_insensitive=True):
2093 q = RepoGroup.query()
2093 q = RepoGroup.query()
2094
2094
2095 if not isinstance(user_id, Optional):
2095 if not isinstance(user_id, Optional):
2096 q = q.filter(RepoGroup.user_id == user_id)
2096 q = q.filter(RepoGroup.user_id == user_id)
2097
2097
2098 if not isinstance(group_id, Optional):
2098 if not isinstance(group_id, Optional):
2099 q = q.filter(RepoGroup.group_parent_id == group_id)
2099 q = q.filter(RepoGroup.group_parent_id == group_id)
2100
2100
2101 if case_insensitive:
2101 if case_insensitive:
2102 q = q.order_by(func.lower(RepoGroup.group_name))
2102 q = q.order_by(func.lower(RepoGroup.group_name))
2103 else:
2103 else:
2104 q = q.order_by(RepoGroup.group_name)
2104 q = q.order_by(RepoGroup.group_name)
2105 return q.all()
2105 return q.all()
2106
2106
2107 @property
2107 @property
2108 def parents(self):
2108 def parents(self):
2109 parents_recursion_limit = 10
2109 parents_recursion_limit = 10
2110 groups = []
2110 groups = []
2111 if self.parent_group is None:
2111 if self.parent_group is None:
2112 return groups
2112 return groups
2113 cur_gr = self.parent_group
2113 cur_gr = self.parent_group
2114 groups.insert(0, cur_gr)
2114 groups.insert(0, cur_gr)
2115 cnt = 0
2115 cnt = 0
2116 while 1:
2116 while 1:
2117 cnt += 1
2117 cnt += 1
2118 gr = getattr(cur_gr, 'parent_group', None)
2118 gr = getattr(cur_gr, 'parent_group', None)
2119 cur_gr = cur_gr.parent_group
2119 cur_gr = cur_gr.parent_group
2120 if gr is None:
2120 if gr is None:
2121 break
2121 break
2122 if cnt == parents_recursion_limit:
2122 if cnt == parents_recursion_limit:
2123 # this will prevent accidental infinit loops
2123 # this will prevent accidental infinit loops
2124 log.error(('more than %s parents found for group %s, stopping '
2124 log.error(('more than %s parents found for group %s, stopping '
2125 'recursive parent fetching' % (parents_recursion_limit, self)))
2125 'recursive parent fetching' % (parents_recursion_limit, self)))
2126 break
2126 break
2127
2127
2128 groups.insert(0, gr)
2128 groups.insert(0, gr)
2129 return groups
2129 return groups
2130
2130
2131 @property
2131 @property
2132 def children(self):
2132 def children(self):
2133 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2133 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2134
2134
2135 @property
2135 @property
2136 def name(self):
2136 def name(self):
2137 return self.group_name.split(RepoGroup.url_sep())[-1]
2137 return self.group_name.split(RepoGroup.url_sep())[-1]
2138
2138
2139 @property
2139 @property
2140 def full_path(self):
2140 def full_path(self):
2141 return self.group_name
2141 return self.group_name
2142
2142
2143 @property
2143 @property
2144 def full_path_splitted(self):
2144 def full_path_splitted(self):
2145 return self.group_name.split(RepoGroup.url_sep())
2145 return self.group_name.split(RepoGroup.url_sep())
2146
2146
2147 @property
2147 @property
2148 def repositories(self):
2148 def repositories(self):
2149 return Repository.query()\
2149 return Repository.query()\
2150 .filter(Repository.group == self)\
2150 .filter(Repository.group == self)\
2151 .order_by(Repository.repo_name)
2151 .order_by(Repository.repo_name)
2152
2152
2153 @property
2153 @property
2154 def repositories_recursive_count(self):
2154 def repositories_recursive_count(self):
2155 cnt = self.repositories.count()
2155 cnt = self.repositories.count()
2156
2156
2157 def children_count(group):
2157 def children_count(group):
2158 cnt = 0
2158 cnt = 0
2159 for child in group.children:
2159 for child in group.children:
2160 cnt += child.repositories.count()
2160 cnt += child.repositories.count()
2161 cnt += children_count(child)
2161 cnt += children_count(child)
2162 return cnt
2162 return cnt
2163
2163
2164 return cnt + children_count(self)
2164 return cnt + children_count(self)
2165
2165
2166 def _recursive_objects(self, include_repos=True):
2166 def _recursive_objects(self, include_repos=True):
2167 all_ = []
2167 all_ = []
2168
2168
2169 def _get_members(root_gr):
2169 def _get_members(root_gr):
2170 if include_repos:
2170 if include_repos:
2171 for r in root_gr.repositories:
2171 for r in root_gr.repositories:
2172 all_.append(r)
2172 all_.append(r)
2173 childs = root_gr.children.all()
2173 childs = root_gr.children.all()
2174 if childs:
2174 if childs:
2175 for gr in childs:
2175 for gr in childs:
2176 all_.append(gr)
2176 all_.append(gr)
2177 _get_members(gr)
2177 _get_members(gr)
2178
2178
2179 _get_members(self)
2179 _get_members(self)
2180 return [self] + all_
2180 return [self] + all_
2181
2181
2182 def recursive_groups_and_repos(self):
2182 def recursive_groups_and_repos(self):
2183 """
2183 """
2184 Recursive return all groups, with repositories in those groups
2184 Recursive return all groups, with repositories in those groups
2185 """
2185 """
2186 return self._recursive_objects()
2186 return self._recursive_objects()
2187
2187
2188 def recursive_groups(self):
2188 def recursive_groups(self):
2189 """
2189 """
2190 Returns all children groups for this group including children of children
2190 Returns all children groups for this group including children of children
2191 """
2191 """
2192 return self._recursive_objects(include_repos=False)
2192 return self._recursive_objects(include_repos=False)
2193
2193
2194 def get_new_name(self, group_name):
2194 def get_new_name(self, group_name):
2195 """
2195 """
2196 returns new full group name based on parent and new name
2196 returns new full group name based on parent and new name
2197
2197
2198 :param group_name:
2198 :param group_name:
2199 """
2199 """
2200 path_prefix = (self.parent_group.full_path_splitted if
2200 path_prefix = (self.parent_group.full_path_splitted if
2201 self.parent_group else [])
2201 self.parent_group else [])
2202 return RepoGroup.url_sep().join(path_prefix + [group_name])
2202 return RepoGroup.url_sep().join(path_prefix + [group_name])
2203
2203
2204 def permissions(self, with_admins=True, with_owner=True):
2204 def permissions(self, with_admins=True, with_owner=True):
2205 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2205 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2206 q = q.options(joinedload(UserRepoGroupToPerm.group),
2206 q = q.options(joinedload(UserRepoGroupToPerm.group),
2207 joinedload(UserRepoGroupToPerm.user),
2207 joinedload(UserRepoGroupToPerm.user),
2208 joinedload(UserRepoGroupToPerm.permission),)
2208 joinedload(UserRepoGroupToPerm.permission),)
2209
2209
2210 # get owners and admins and permissions. We do a trick of re-writing
2210 # get owners and admins and permissions. We do a trick of re-writing
2211 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2211 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2212 # has a global reference and changing one object propagates to all
2212 # has a global reference and changing one object propagates to all
2213 # others. This means if admin is also an owner admin_row that change
2213 # others. This means if admin is also an owner admin_row that change
2214 # would propagate to both objects
2214 # would propagate to both objects
2215 perm_rows = []
2215 perm_rows = []
2216 for _usr in q.all():
2216 for _usr in q.all():
2217 usr = AttributeDict(_usr.user.get_dict())
2217 usr = AttributeDict(_usr.user.get_dict())
2218 usr.permission = _usr.permission.permission_name
2218 usr.permission = _usr.permission.permission_name
2219 perm_rows.append(usr)
2219 perm_rows.append(usr)
2220
2220
2221 # filter the perm rows by 'default' first and then sort them by
2221 # filter the perm rows by 'default' first and then sort them by
2222 # admin,write,read,none permissions sorted again alphabetically in
2222 # admin,write,read,none permissions sorted again alphabetically in
2223 # each group
2223 # each group
2224 perm_rows = sorted(perm_rows, key=display_sort)
2224 perm_rows = sorted(perm_rows, key=display_sort)
2225
2225
2226 _admin_perm = 'group.admin'
2226 _admin_perm = 'group.admin'
2227 owner_row = []
2227 owner_row = []
2228 if with_owner:
2228 if with_owner:
2229 usr = AttributeDict(self.user.get_dict())
2229 usr = AttributeDict(self.user.get_dict())
2230 usr.owner_row = True
2230 usr.owner_row = True
2231 usr.permission = _admin_perm
2231 usr.permission = _admin_perm
2232 owner_row.append(usr)
2232 owner_row.append(usr)
2233
2233
2234 super_admin_rows = []
2234 super_admin_rows = []
2235 if with_admins:
2235 if with_admins:
2236 for usr in User.get_all_super_admins():
2236 for usr in User.get_all_super_admins():
2237 # if this admin is also owner, don't double the record
2237 # if this admin is also owner, don't double the record
2238 if usr.user_id == owner_row[0].user_id:
2238 if usr.user_id == owner_row[0].user_id:
2239 owner_row[0].admin_row = True
2239 owner_row[0].admin_row = True
2240 else:
2240 else:
2241 usr = AttributeDict(usr.get_dict())
2241 usr = AttributeDict(usr.get_dict())
2242 usr.admin_row = True
2242 usr.admin_row = True
2243 usr.permission = _admin_perm
2243 usr.permission = _admin_perm
2244 super_admin_rows.append(usr)
2244 super_admin_rows.append(usr)
2245
2245
2246 return super_admin_rows + owner_row + perm_rows
2246 return super_admin_rows + owner_row + perm_rows
2247
2247
2248 def permission_user_groups(self):
2248 def permission_user_groups(self):
2249 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2249 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2250 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2250 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2251 joinedload(UserGroupRepoGroupToPerm.users_group),
2251 joinedload(UserGroupRepoGroupToPerm.users_group),
2252 joinedload(UserGroupRepoGroupToPerm.permission),)
2252 joinedload(UserGroupRepoGroupToPerm.permission),)
2253
2253
2254 perm_rows = []
2254 perm_rows = []
2255 for _user_group in q.all():
2255 for _user_group in q.all():
2256 usr = AttributeDict(_user_group.users_group.get_dict())
2256 usr = AttributeDict(_user_group.users_group.get_dict())
2257 usr.permission = _user_group.permission.permission_name
2257 usr.permission = _user_group.permission.permission_name
2258 perm_rows.append(usr)
2258 perm_rows.append(usr)
2259
2259
2260 return perm_rows
2260 return perm_rows
2261
2261
2262 def get_api_data(self):
2262 def get_api_data(self):
2263 """
2263 """
2264 Common function for generating api data
2264 Common function for generating api data
2265
2265
2266 """
2266 """
2267 group = self
2267 group = self
2268 data = {
2268 data = {
2269 'group_id': group.group_id,
2269 'group_id': group.group_id,
2270 'group_name': group.group_name,
2270 'group_name': group.group_name,
2271 'group_description': group.group_description,
2271 'group_description': group.group_description,
2272 'parent_group': group.parent_group.group_name if group.parent_group else None,
2272 'parent_group': group.parent_group.group_name if group.parent_group else None,
2273 'repositories': [x.repo_name for x in group.repositories],
2273 'repositories': [x.repo_name for x in group.repositories],
2274 'owner': group.user.username,
2274 'owner': group.user.username,
2275 }
2275 }
2276 return data
2276 return data
2277
2277
2278
2278
2279 class Permission(Base, BaseModel):
2279 class Permission(Base, BaseModel):
2280 __tablename__ = 'permissions'
2280 __tablename__ = 'permissions'
2281 __table_args__ = (
2281 __table_args__ = (
2282 Index('p_perm_name_idx', 'permission_name'),
2282 Index('p_perm_name_idx', 'permission_name'),
2283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2285 )
2285 )
2286 PERMS = [
2286 PERMS = [
2287 ('hg.admin', _('RhodeCode Super Administrator')),
2287 ('hg.admin', _('RhodeCode Super Administrator')),
2288
2288
2289 ('repository.none', _('Repository no access')),
2289 ('repository.none', _('Repository no access')),
2290 ('repository.read', _('Repository read access')),
2290 ('repository.read', _('Repository read access')),
2291 ('repository.write', _('Repository write access')),
2291 ('repository.write', _('Repository write access')),
2292 ('repository.admin', _('Repository admin access')),
2292 ('repository.admin', _('Repository admin access')),
2293
2293
2294 ('group.none', _('Repository group no access')),
2294 ('group.none', _('Repository group no access')),
2295 ('group.read', _('Repository group read access')),
2295 ('group.read', _('Repository group read access')),
2296 ('group.write', _('Repository group write access')),
2296 ('group.write', _('Repository group write access')),
2297 ('group.admin', _('Repository group admin access')),
2297 ('group.admin', _('Repository group admin access')),
2298
2298
2299 ('usergroup.none', _('User group no access')),
2299 ('usergroup.none', _('User group no access')),
2300 ('usergroup.read', _('User group read access')),
2300 ('usergroup.read', _('User group read access')),
2301 ('usergroup.write', _('User group write access')),
2301 ('usergroup.write', _('User group write access')),
2302 ('usergroup.admin', _('User group admin access')),
2302 ('usergroup.admin', _('User group admin access')),
2303
2303
2304 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2304 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2305 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2305 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2306
2306
2307 ('hg.usergroup.create.false', _('User Group creation disabled')),
2307 ('hg.usergroup.create.false', _('User Group creation disabled')),
2308 ('hg.usergroup.create.true', _('User Group creation enabled')),
2308 ('hg.usergroup.create.true', _('User Group creation enabled')),
2309
2309
2310 ('hg.create.none', _('Repository creation disabled')),
2310 ('hg.create.none', _('Repository creation disabled')),
2311 ('hg.create.repository', _('Repository creation enabled')),
2311 ('hg.create.repository', _('Repository creation enabled')),
2312 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2312 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2313 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2313 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2314
2314
2315 ('hg.fork.none', _('Repository forking disabled')),
2315 ('hg.fork.none', _('Repository forking disabled')),
2316 ('hg.fork.repository', _('Repository forking enabled')),
2316 ('hg.fork.repository', _('Repository forking enabled')),
2317
2317
2318 ('hg.register.none', _('Registration disabled')),
2318 ('hg.register.none', _('Registration disabled')),
2319 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2319 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2320 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2320 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2321
2321
2322 ('hg.extern_activate.manual', _('Manual activation of external account')),
2322 ('hg.extern_activate.manual', _('Manual activation of external account')),
2323 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2323 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2324
2324
2325 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2325 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2326 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2326 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2327 ]
2327 ]
2328
2328
2329 # definition of system default permissions for DEFAULT user
2329 # definition of system default permissions for DEFAULT user
2330 DEFAULT_USER_PERMISSIONS = [
2330 DEFAULT_USER_PERMISSIONS = [
2331 'repository.read',
2331 'repository.read',
2332 'group.read',
2332 'group.read',
2333 'usergroup.read',
2333 'usergroup.read',
2334 'hg.create.repository',
2334 'hg.create.repository',
2335 'hg.repogroup.create.false',
2335 'hg.repogroup.create.false',
2336 'hg.usergroup.create.false',
2336 'hg.usergroup.create.false',
2337 'hg.create.write_on_repogroup.true',
2337 'hg.create.write_on_repogroup.true',
2338 'hg.fork.repository',
2338 'hg.fork.repository',
2339 'hg.register.manual_activate',
2339 'hg.register.manual_activate',
2340 'hg.extern_activate.auto',
2340 'hg.extern_activate.auto',
2341 'hg.inherit_default_perms.true',
2341 'hg.inherit_default_perms.true',
2342 ]
2342 ]
2343
2343
2344 # defines which permissions are more important higher the more important
2344 # defines which permissions are more important higher the more important
2345 # Weight defines which permissions are more important.
2345 # Weight defines which permissions are more important.
2346 # The higher number the more important.
2346 # The higher number the more important.
2347 PERM_WEIGHTS = {
2347 PERM_WEIGHTS = {
2348 'repository.none': 0,
2348 'repository.none': 0,
2349 'repository.read': 1,
2349 'repository.read': 1,
2350 'repository.write': 3,
2350 'repository.write': 3,
2351 'repository.admin': 4,
2351 'repository.admin': 4,
2352
2352
2353 'group.none': 0,
2353 'group.none': 0,
2354 'group.read': 1,
2354 'group.read': 1,
2355 'group.write': 3,
2355 'group.write': 3,
2356 'group.admin': 4,
2356 'group.admin': 4,
2357
2357
2358 'usergroup.none': 0,
2358 'usergroup.none': 0,
2359 'usergroup.read': 1,
2359 'usergroup.read': 1,
2360 'usergroup.write': 3,
2360 'usergroup.write': 3,
2361 'usergroup.admin': 4,
2361 'usergroup.admin': 4,
2362
2362
2363 'hg.repogroup.create.false': 0,
2363 'hg.repogroup.create.false': 0,
2364 'hg.repogroup.create.true': 1,
2364 'hg.repogroup.create.true': 1,
2365
2365
2366 'hg.usergroup.create.false': 0,
2366 'hg.usergroup.create.false': 0,
2367 'hg.usergroup.create.true': 1,
2367 'hg.usergroup.create.true': 1,
2368
2368
2369 'hg.fork.none': 0,
2369 'hg.fork.none': 0,
2370 'hg.fork.repository': 1,
2370 'hg.fork.repository': 1,
2371 'hg.create.none': 0,
2371 'hg.create.none': 0,
2372 'hg.create.repository': 1
2372 'hg.create.repository': 1
2373 }
2373 }
2374
2374
2375 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2375 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2376 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2376 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2377 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2377 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2378
2378
2379 def __unicode__(self):
2379 def __unicode__(self):
2380 return u"<%s('%s:%s')>" % (
2380 return u"<%s('%s:%s')>" % (
2381 self.__class__.__name__, self.permission_id, self.permission_name
2381 self.__class__.__name__, self.permission_id, self.permission_name
2382 )
2382 )
2383
2383
2384 @classmethod
2384 @classmethod
2385 def get_by_key(cls, key):
2385 def get_by_key(cls, key):
2386 return cls.query().filter(cls.permission_name == key).scalar()
2386 return cls.query().filter(cls.permission_name == key).scalar()
2387
2387
2388 @classmethod
2388 @classmethod
2389 def get_default_repo_perms(cls, user_id, repo_id=None):
2389 def get_default_repo_perms(cls, user_id, repo_id=None):
2390 q = Session().query(UserRepoToPerm, Repository, Permission)\
2390 q = Session().query(UserRepoToPerm, Repository, Permission)\
2391 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2391 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2392 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2392 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2393 .filter(UserRepoToPerm.user_id == user_id)
2393 .filter(UserRepoToPerm.user_id == user_id)
2394 if repo_id:
2394 if repo_id:
2395 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2395 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2396 return q.all()
2396 return q.all()
2397
2397
2398 @classmethod
2398 @classmethod
2399 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2399 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2400 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2400 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2401 .join(
2401 .join(
2402 Permission,
2402 Permission,
2403 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2403 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2404 .join(
2404 .join(
2405 Repository,
2405 Repository,
2406 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2406 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2407 .join(
2407 .join(
2408 UserGroup,
2408 UserGroup,
2409 UserGroupRepoToPerm.users_group_id ==
2409 UserGroupRepoToPerm.users_group_id ==
2410 UserGroup.users_group_id)\
2410 UserGroup.users_group_id)\
2411 .join(
2411 .join(
2412 UserGroupMember,
2412 UserGroupMember,
2413 UserGroupRepoToPerm.users_group_id ==
2413 UserGroupRepoToPerm.users_group_id ==
2414 UserGroupMember.users_group_id)\
2414 UserGroupMember.users_group_id)\
2415 .filter(
2415 .filter(
2416 UserGroupMember.user_id == user_id,
2416 UserGroupMember.user_id == user_id,
2417 UserGroup.users_group_active == true())
2417 UserGroup.users_group_active == true())
2418 if repo_id:
2418 if repo_id:
2419 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2419 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2420 return q.all()
2420 return q.all()
2421
2421
2422 @classmethod
2422 @classmethod
2423 def get_default_group_perms(cls, user_id, repo_group_id=None):
2423 def get_default_group_perms(cls, user_id, repo_group_id=None):
2424 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2424 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2425 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2425 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2426 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2426 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2427 .filter(UserRepoGroupToPerm.user_id == user_id)
2427 .filter(UserRepoGroupToPerm.user_id == user_id)
2428 if repo_group_id:
2428 if repo_group_id:
2429 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2429 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2430 return q.all()
2430 return q.all()
2431
2431
2432 @classmethod
2432 @classmethod
2433 def get_default_group_perms_from_user_group(
2433 def get_default_group_perms_from_user_group(
2434 cls, user_id, repo_group_id=None):
2434 cls, user_id, repo_group_id=None):
2435 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2435 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2436 .join(
2436 .join(
2437 Permission,
2437 Permission,
2438 UserGroupRepoGroupToPerm.permission_id ==
2438 UserGroupRepoGroupToPerm.permission_id ==
2439 Permission.permission_id)\
2439 Permission.permission_id)\
2440 .join(
2440 .join(
2441 RepoGroup,
2441 RepoGroup,
2442 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2442 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2443 .join(
2443 .join(
2444 UserGroup,
2444 UserGroup,
2445 UserGroupRepoGroupToPerm.users_group_id ==
2445 UserGroupRepoGroupToPerm.users_group_id ==
2446 UserGroup.users_group_id)\
2446 UserGroup.users_group_id)\
2447 .join(
2447 .join(
2448 UserGroupMember,
2448 UserGroupMember,
2449 UserGroupRepoGroupToPerm.users_group_id ==
2449 UserGroupRepoGroupToPerm.users_group_id ==
2450 UserGroupMember.users_group_id)\
2450 UserGroupMember.users_group_id)\
2451 .filter(
2451 .filter(
2452 UserGroupMember.user_id == user_id,
2452 UserGroupMember.user_id == user_id,
2453 UserGroup.users_group_active == true())
2453 UserGroup.users_group_active == true())
2454 if repo_group_id:
2454 if repo_group_id:
2455 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2455 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2456 return q.all()
2456 return q.all()
2457
2457
2458 @classmethod
2458 @classmethod
2459 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2459 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2460 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2460 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2461 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2461 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2462 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2462 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2463 .filter(UserUserGroupToPerm.user_id == user_id)
2463 .filter(UserUserGroupToPerm.user_id == user_id)
2464 if user_group_id:
2464 if user_group_id:
2465 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2465 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2466 return q.all()
2466 return q.all()
2467
2467
2468 @classmethod
2468 @classmethod
2469 def get_default_user_group_perms_from_user_group(
2469 def get_default_user_group_perms_from_user_group(
2470 cls, user_id, user_group_id=None):
2470 cls, user_id, user_group_id=None):
2471 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2471 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2472 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2472 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2473 .join(
2473 .join(
2474 Permission,
2474 Permission,
2475 UserGroupUserGroupToPerm.permission_id ==
2475 UserGroupUserGroupToPerm.permission_id ==
2476 Permission.permission_id)\
2476 Permission.permission_id)\
2477 .join(
2477 .join(
2478 TargetUserGroup,
2478 TargetUserGroup,
2479 UserGroupUserGroupToPerm.target_user_group_id ==
2479 UserGroupUserGroupToPerm.target_user_group_id ==
2480 TargetUserGroup.users_group_id)\
2480 TargetUserGroup.users_group_id)\
2481 .join(
2481 .join(
2482 UserGroup,
2482 UserGroup,
2483 UserGroupUserGroupToPerm.user_group_id ==
2483 UserGroupUserGroupToPerm.user_group_id ==
2484 UserGroup.users_group_id)\
2484 UserGroup.users_group_id)\
2485 .join(
2485 .join(
2486 UserGroupMember,
2486 UserGroupMember,
2487 UserGroupUserGroupToPerm.user_group_id ==
2487 UserGroupUserGroupToPerm.user_group_id ==
2488 UserGroupMember.users_group_id)\
2488 UserGroupMember.users_group_id)\
2489 .filter(
2489 .filter(
2490 UserGroupMember.user_id == user_id,
2490 UserGroupMember.user_id == user_id,
2491 UserGroup.users_group_active == true())
2491 UserGroup.users_group_active == true())
2492 if user_group_id:
2492 if user_group_id:
2493 q = q.filter(
2493 q = q.filter(
2494 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2494 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2495
2495
2496 return q.all()
2496 return q.all()
2497
2497
2498
2498
2499 class UserRepoToPerm(Base, BaseModel):
2499 class UserRepoToPerm(Base, BaseModel):
2500 __tablename__ = 'repo_to_perm'
2500 __tablename__ = 'repo_to_perm'
2501 __table_args__ = (
2501 __table_args__ = (
2502 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2502 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2505 )
2505 )
2506 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2506 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2507 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2507 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2509 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2509 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2510
2510
2511 user = relationship('User')
2511 user = relationship('User')
2512 repository = relationship('Repository')
2512 repository = relationship('Repository')
2513 permission = relationship('Permission')
2513 permission = relationship('Permission')
2514
2514
2515 @classmethod
2515 @classmethod
2516 def create(cls, user, repository, permission):
2516 def create(cls, user, repository, permission):
2517 n = cls()
2517 n = cls()
2518 n.user = user
2518 n.user = user
2519 n.repository = repository
2519 n.repository = repository
2520 n.permission = permission
2520 n.permission = permission
2521 Session().add(n)
2521 Session().add(n)
2522 return n
2522 return n
2523
2523
2524 def __unicode__(self):
2524 def __unicode__(self):
2525 return u'<%s => %s >' % (self.user, self.repository)
2525 return u'<%s => %s >' % (self.user, self.repository)
2526
2526
2527
2527
2528 class UserUserGroupToPerm(Base, BaseModel):
2528 class UserUserGroupToPerm(Base, BaseModel):
2529 __tablename__ = 'user_user_group_to_perm'
2529 __tablename__ = 'user_user_group_to_perm'
2530 __table_args__ = (
2530 __table_args__ = (
2531 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2531 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2534 )
2534 )
2535 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2535 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2537 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2538 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2539
2539
2540 user = relationship('User')
2540 user = relationship('User')
2541 user_group = relationship('UserGroup')
2541 user_group = relationship('UserGroup')
2542 permission = relationship('Permission')
2542 permission = relationship('Permission')
2543
2543
2544 @classmethod
2544 @classmethod
2545 def create(cls, user, user_group, permission):
2545 def create(cls, user, user_group, permission):
2546 n = cls()
2546 n = cls()
2547 n.user = user
2547 n.user = user
2548 n.user_group = user_group
2548 n.user_group = user_group
2549 n.permission = permission
2549 n.permission = permission
2550 Session().add(n)
2550 Session().add(n)
2551 return n
2551 return n
2552
2552
2553 def __unicode__(self):
2553 def __unicode__(self):
2554 return u'<%s => %s >' % (self.user, self.user_group)
2554 return u'<%s => %s >' % (self.user, self.user_group)
2555
2555
2556
2556
2557 class UserToPerm(Base, BaseModel):
2557 class UserToPerm(Base, BaseModel):
2558 __tablename__ = 'user_to_perm'
2558 __tablename__ = 'user_to_perm'
2559 __table_args__ = (
2559 __table_args__ = (
2560 UniqueConstraint('user_id', 'permission_id'),
2560 UniqueConstraint('user_id', 'permission_id'),
2561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2562 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2562 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2563 )
2563 )
2564 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2564 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2567
2567
2568 user = relationship('User')
2568 user = relationship('User')
2569 permission = relationship('Permission', lazy='joined')
2569 permission = relationship('Permission', lazy='joined')
2570
2570
2571 def __unicode__(self):
2571 def __unicode__(self):
2572 return u'<%s => %s >' % (self.user, self.permission)
2572 return u'<%s => %s >' % (self.user, self.permission)
2573
2573
2574
2574
2575 class UserGroupRepoToPerm(Base, BaseModel):
2575 class UserGroupRepoToPerm(Base, BaseModel):
2576 __tablename__ = 'users_group_repo_to_perm'
2576 __tablename__ = 'users_group_repo_to_perm'
2577 __table_args__ = (
2577 __table_args__ = (
2578 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2578 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2579 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2579 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2580 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2580 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2581 )
2581 )
2582 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2582 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2583 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2583 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2584 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2584 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2585 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2585 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2586
2586
2587 users_group = relationship('UserGroup')
2587 users_group = relationship('UserGroup')
2588 permission = relationship('Permission')
2588 permission = relationship('Permission')
2589 repository = relationship('Repository')
2589 repository = relationship('Repository')
2590
2590
2591 @classmethod
2591 @classmethod
2592 def create(cls, users_group, repository, permission):
2592 def create(cls, users_group, repository, permission):
2593 n = cls()
2593 n = cls()
2594 n.users_group = users_group
2594 n.users_group = users_group
2595 n.repository = repository
2595 n.repository = repository
2596 n.permission = permission
2596 n.permission = permission
2597 Session().add(n)
2597 Session().add(n)
2598 return n
2598 return n
2599
2599
2600 def __unicode__(self):
2600 def __unicode__(self):
2601 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2601 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2602
2602
2603
2603
2604 class UserGroupUserGroupToPerm(Base, BaseModel):
2604 class UserGroupUserGroupToPerm(Base, BaseModel):
2605 __tablename__ = 'user_group_user_group_to_perm'
2605 __tablename__ = 'user_group_user_group_to_perm'
2606 __table_args__ = (
2606 __table_args__ = (
2607 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2607 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2608 CheckConstraint('target_user_group_id != user_group_id'),
2608 CheckConstraint('target_user_group_id != user_group_id'),
2609 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2609 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2610 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2610 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2611 )
2611 )
2612 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2612 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2613 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2613 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2614 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2614 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2615 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2615 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2616
2616
2617 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2617 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2618 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2618 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2619 permission = relationship('Permission')
2619 permission = relationship('Permission')
2620
2620
2621 @classmethod
2621 @classmethod
2622 def create(cls, target_user_group, user_group, permission):
2622 def create(cls, target_user_group, user_group, permission):
2623 n = cls()
2623 n = cls()
2624 n.target_user_group = target_user_group
2624 n.target_user_group = target_user_group
2625 n.user_group = user_group
2625 n.user_group = user_group
2626 n.permission = permission
2626 n.permission = permission
2627 Session().add(n)
2627 Session().add(n)
2628 return n
2628 return n
2629
2629
2630 def __unicode__(self):
2630 def __unicode__(self):
2631 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2631 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2632
2632
2633
2633
2634 class UserGroupToPerm(Base, BaseModel):
2634 class UserGroupToPerm(Base, BaseModel):
2635 __tablename__ = 'users_group_to_perm'
2635 __tablename__ = 'users_group_to_perm'
2636 __table_args__ = (
2636 __table_args__ = (
2637 UniqueConstraint('users_group_id', 'permission_id',),
2637 UniqueConstraint('users_group_id', 'permission_id',),
2638 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2638 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2639 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2639 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2640 )
2640 )
2641 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2641 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2642 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2642 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2643 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2643 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2644
2644
2645 users_group = relationship('UserGroup')
2645 users_group = relationship('UserGroup')
2646 permission = relationship('Permission')
2646 permission = relationship('Permission')
2647
2647
2648
2648
2649 class UserRepoGroupToPerm(Base, BaseModel):
2649 class UserRepoGroupToPerm(Base, BaseModel):
2650 __tablename__ = 'user_repo_group_to_perm'
2650 __tablename__ = 'user_repo_group_to_perm'
2651 __table_args__ = (
2651 __table_args__ = (
2652 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2652 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2653 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2653 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2654 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2654 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2655 )
2655 )
2656
2656
2657 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2657 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2658 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2658 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2659 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2659 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2661
2661
2662 user = relationship('User')
2662 user = relationship('User')
2663 group = relationship('RepoGroup')
2663 group = relationship('RepoGroup')
2664 permission = relationship('Permission')
2664 permission = relationship('Permission')
2665
2665
2666 @classmethod
2666 @classmethod
2667 def create(cls, user, repository_group, permission):
2667 def create(cls, user, repository_group, permission):
2668 n = cls()
2668 n = cls()
2669 n.user = user
2669 n.user = user
2670 n.group = repository_group
2670 n.group = repository_group
2671 n.permission = permission
2671 n.permission = permission
2672 Session().add(n)
2672 Session().add(n)
2673 return n
2673 return n
2674
2674
2675
2675
2676 class UserGroupRepoGroupToPerm(Base, BaseModel):
2676 class UserGroupRepoGroupToPerm(Base, BaseModel):
2677 __tablename__ = 'users_group_repo_group_to_perm'
2677 __tablename__ = 'users_group_repo_group_to_perm'
2678 __table_args__ = (
2678 __table_args__ = (
2679 UniqueConstraint('users_group_id', 'group_id'),
2679 UniqueConstraint('users_group_id', 'group_id'),
2680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2681 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2681 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2682 )
2682 )
2683
2683
2684 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2684 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2686 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2686 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2687 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2688
2688
2689 users_group = relationship('UserGroup')
2689 users_group = relationship('UserGroup')
2690 permission = relationship('Permission')
2690 permission = relationship('Permission')
2691 group = relationship('RepoGroup')
2691 group = relationship('RepoGroup')
2692
2692
2693 @classmethod
2693 @classmethod
2694 def create(cls, user_group, repository_group, permission):
2694 def create(cls, user_group, repository_group, permission):
2695 n = cls()
2695 n = cls()
2696 n.users_group = user_group
2696 n.users_group = user_group
2697 n.group = repository_group
2697 n.group = repository_group
2698 n.permission = permission
2698 n.permission = permission
2699 Session().add(n)
2699 Session().add(n)
2700 return n
2700 return n
2701
2701
2702 def __unicode__(self):
2702 def __unicode__(self):
2703 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2703 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2704
2704
2705
2705
2706 class Statistics(Base, BaseModel):
2706 class Statistics(Base, BaseModel):
2707 __tablename__ = 'statistics'
2707 __tablename__ = 'statistics'
2708 __table_args__ = (
2708 __table_args__ = (
2709 UniqueConstraint('repository_id'),
2709 UniqueConstraint('repository_id'),
2710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2712 )
2712 )
2713 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2713 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2714 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2714 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2715 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2715 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2716 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2716 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2717 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2717 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2718 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2718 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2719
2719
2720 repository = relationship('Repository', single_parent=True)
2720 repository = relationship('Repository', single_parent=True)
2721
2721
2722
2722
2723 class UserFollowing(Base, BaseModel):
2723 class UserFollowing(Base, BaseModel):
2724 __tablename__ = 'user_followings'
2724 __tablename__ = 'user_followings'
2725 __table_args__ = (
2725 __table_args__ = (
2726 UniqueConstraint('user_id', 'follows_repository_id'),
2726 UniqueConstraint('user_id', 'follows_repository_id'),
2727 UniqueConstraint('user_id', 'follows_user_id'),
2727 UniqueConstraint('user_id', 'follows_user_id'),
2728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2730 )
2730 )
2731
2731
2732 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2732 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2734 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2734 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2735 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2735 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2736 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2736 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2737
2737
2738 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2738 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2739
2739
2740 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2740 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2741 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2741 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2742
2742
2743 @classmethod
2743 @classmethod
2744 def get_repo_followers(cls, repo_id):
2744 def get_repo_followers(cls, repo_id):
2745 return cls.query().filter(cls.follows_repo_id == repo_id)
2745 return cls.query().filter(cls.follows_repo_id == repo_id)
2746
2746
2747
2747
2748 class CacheKey(Base, BaseModel):
2748 class CacheKey(Base, BaseModel):
2749 __tablename__ = 'cache_invalidation'
2749 __tablename__ = 'cache_invalidation'
2750 __table_args__ = (
2750 __table_args__ = (
2751 UniqueConstraint('cache_key'),
2751 UniqueConstraint('cache_key'),
2752 Index('key_idx', 'cache_key'),
2752 Index('key_idx', 'cache_key'),
2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2755 )
2755 )
2756 CACHE_TYPE_ATOM = 'ATOM'
2756 CACHE_TYPE_ATOM = 'ATOM'
2757 CACHE_TYPE_RSS = 'RSS'
2757 CACHE_TYPE_RSS = 'RSS'
2758 CACHE_TYPE_README = 'README'
2758 CACHE_TYPE_README = 'README'
2759
2759
2760 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2760 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2761 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2761 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2762 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2762 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2763 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2763 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2764
2764
2765 def __init__(self, cache_key, cache_args=''):
2765 def __init__(self, cache_key, cache_args=''):
2766 self.cache_key = cache_key
2766 self.cache_key = cache_key
2767 self.cache_args = cache_args
2767 self.cache_args = cache_args
2768 self.cache_active = False
2768 self.cache_active = False
2769
2769
2770 def __unicode__(self):
2770 def __unicode__(self):
2771 return u"<%s('%s:%s[%s]')>" % (
2771 return u"<%s('%s:%s[%s]')>" % (
2772 self.__class__.__name__,
2772 self.__class__.__name__,
2773 self.cache_id, self.cache_key, self.cache_active)
2773 self.cache_id, self.cache_key, self.cache_active)
2774
2774
2775 def _cache_key_partition(self):
2775 def _cache_key_partition(self):
2776 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2776 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2777 return prefix, repo_name, suffix
2777 return prefix, repo_name, suffix
2778
2778
2779 def get_prefix(self):
2779 def get_prefix(self):
2780 """
2780 """
2781 Try to extract prefix from existing cache key. The key could consist
2781 Try to extract prefix from existing cache key. The key could consist
2782 of prefix, repo_name, suffix
2782 of prefix, repo_name, suffix
2783 """
2783 """
2784 # this returns prefix, repo_name, suffix
2784 # this returns prefix, repo_name, suffix
2785 return self._cache_key_partition()[0]
2785 return self._cache_key_partition()[0]
2786
2786
2787 def get_suffix(self):
2787 def get_suffix(self):
2788 """
2788 """
2789 get suffix that might have been used in _get_cache_key to
2789 get suffix that might have been used in _get_cache_key to
2790 generate self.cache_key. Only used for informational purposes
2790 generate self.cache_key. Only used for informational purposes
2791 in repo_edit.html.
2791 in repo_edit.html.
2792 """
2792 """
2793 # prefix, repo_name, suffix
2793 # prefix, repo_name, suffix
2794 return self._cache_key_partition()[2]
2794 return self._cache_key_partition()[2]
2795
2795
2796 @classmethod
2796 @classmethod
2797 def delete_all_cache(cls):
2797 def delete_all_cache(cls):
2798 """
2798 """
2799 Delete all cache keys from database.
2799 Delete all cache keys from database.
2800 Should only be run when all instances are down and all entries
2800 Should only be run when all instances are down and all entries
2801 thus stale.
2801 thus stale.
2802 """
2802 """
2803 cls.query().delete()
2803 cls.query().delete()
2804 Session().commit()
2804 Session().commit()
2805
2805
2806 @classmethod
2806 @classmethod
2807 def get_cache_key(cls, repo_name, cache_type):
2807 def get_cache_key(cls, repo_name, cache_type):
2808 """
2808 """
2809
2809
2810 Generate a cache key for this process of RhodeCode instance.
2810 Generate a cache key for this process of RhodeCode instance.
2811 Prefix most likely will be process id or maybe explicitly set
2811 Prefix most likely will be process id or maybe explicitly set
2812 instance_id from .ini file.
2812 instance_id from .ini file.
2813 """
2813 """
2814 import rhodecode
2814 import rhodecode
2815 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2815 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2816
2816
2817 repo_as_unicode = safe_unicode(repo_name)
2817 repo_as_unicode = safe_unicode(repo_name)
2818 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2818 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2819 if cache_type else repo_as_unicode
2819 if cache_type else repo_as_unicode
2820
2820
2821 return u'{}{}'.format(prefix, key)
2821 return u'{}{}'.format(prefix, key)
2822
2822
2823 @classmethod
2823 @classmethod
2824 def set_invalidate(cls, repo_name, delete=False):
2824 def set_invalidate(cls, repo_name, delete=False):
2825 """
2825 """
2826 Mark all caches of a repo as invalid in the database.
2826 Mark all caches of a repo as invalid in the database.
2827 """
2827 """
2828
2828
2829 try:
2829 try:
2830 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2830 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2831 if delete:
2831 if delete:
2832 log.debug('cache objects deleted for repo %s',
2832 log.debug('cache objects deleted for repo %s',
2833 safe_str(repo_name))
2833 safe_str(repo_name))
2834 qry.delete()
2834 qry.delete()
2835 else:
2835 else:
2836 log.debug('cache objects marked as invalid for repo %s',
2836 log.debug('cache objects marked as invalid for repo %s',
2837 safe_str(repo_name))
2837 safe_str(repo_name))
2838 qry.update({"cache_active": False})
2838 qry.update({"cache_active": False})
2839
2839
2840 Session().commit()
2840 Session().commit()
2841 except Exception:
2841 except Exception:
2842 log.exception(
2842 log.exception(
2843 'Cache key invalidation failed for repository %s',
2843 'Cache key invalidation failed for repository %s',
2844 safe_str(repo_name))
2844 safe_str(repo_name))
2845 Session().rollback()
2845 Session().rollback()
2846
2846
2847 @classmethod
2847 @classmethod
2848 def get_active_cache(cls, cache_key):
2848 def get_active_cache(cls, cache_key):
2849 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2849 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2850 if inv_obj:
2850 if inv_obj:
2851 return inv_obj
2851 return inv_obj
2852 return None
2852 return None
2853
2853
2854 @classmethod
2854 @classmethod
2855 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2855 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2856 thread_scoped=False):
2856 thread_scoped=False):
2857 """
2857 """
2858 @cache_region('long_term')
2858 @cache_region('long_term')
2859 def _heavy_calculation(cache_key):
2859 def _heavy_calculation(cache_key):
2860 return 'result'
2860 return 'result'
2861
2861
2862 cache_context = CacheKey.repo_context_cache(
2862 cache_context = CacheKey.repo_context_cache(
2863 _heavy_calculation, repo_name, cache_type)
2863 _heavy_calculation, repo_name, cache_type)
2864
2864
2865 with cache_context as context:
2865 with cache_context as context:
2866 context.invalidate()
2866 context.invalidate()
2867 computed = context.compute()
2867 computed = context.compute()
2868
2868
2869 assert computed == 'result'
2869 assert computed == 'result'
2870 """
2870 """
2871 from rhodecode.lib import caches
2871 from rhodecode.lib import caches
2872 return caches.InvalidationContext(
2872 return caches.InvalidationContext(
2873 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2873 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2874
2874
2875
2875
2876 class ChangesetComment(Base, BaseModel):
2876 class ChangesetComment(Base, BaseModel):
2877 __tablename__ = 'changeset_comments'
2877 __tablename__ = 'changeset_comments'
2878 __table_args__ = (
2878 __table_args__ = (
2879 Index('cc_revision_idx', 'revision'),
2879 Index('cc_revision_idx', 'revision'),
2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2882 )
2882 )
2883
2883
2884 COMMENT_OUTDATED = u'comment_outdated'
2884 COMMENT_OUTDATED = u'comment_outdated'
2885
2885
2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2888 revision = Column('revision', String(40), nullable=True)
2888 revision = Column('revision', String(40), nullable=True)
2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2891 line_no = Column('line_no', Unicode(10), nullable=True)
2891 line_no = Column('line_no', Unicode(10), nullable=True)
2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2898 renderer = Column('renderer', Unicode(64), nullable=True)
2898 renderer = Column('renderer', Unicode(64), nullable=True)
2899 display_state = Column('display_state', Unicode(128), nullable=True)
2899 display_state = Column('display_state', Unicode(128), nullable=True)
2900
2900
2901 author = relationship('User', lazy='joined')
2901 author = relationship('User', lazy='joined')
2902 repo = relationship('Repository')
2902 repo = relationship('Repository')
2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2904 pull_request = relationship('PullRequest', lazy='joined')
2904 pull_request = relationship('PullRequest', lazy='joined')
2905 pull_request_version = relationship('PullRequestVersion')
2905 pull_request_version = relationship('PullRequestVersion')
2906
2906
2907 @classmethod
2907 @classmethod
2908 def get_users(cls, revision=None, pull_request_id=None):
2908 def get_users(cls, revision=None, pull_request_id=None):
2909 """
2909 """
2910 Returns user associated with this ChangesetComment. ie those
2910 Returns user associated with this ChangesetComment. ie those
2911 who actually commented
2911 who actually commented
2912
2912
2913 :param cls:
2913 :param cls:
2914 :param revision:
2914 :param revision:
2915 """
2915 """
2916 q = Session().query(User)\
2916 q = Session().query(User)\
2917 .join(ChangesetComment.author)
2917 .join(ChangesetComment.author)
2918 if revision:
2918 if revision:
2919 q = q.filter(cls.revision == revision)
2919 q = q.filter(cls.revision == revision)
2920 elif pull_request_id:
2920 elif pull_request_id:
2921 q = q.filter(cls.pull_request_id == pull_request_id)
2921 q = q.filter(cls.pull_request_id == pull_request_id)
2922 return q.all()
2922 return q.all()
2923
2923
2924 def render(self, mentions=False):
2924 def render(self, mentions=False):
2925 from rhodecode.lib import helpers as h
2925 from rhodecode.lib import helpers as h
2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2927
2927
2928 def __repr__(self):
2928 def __repr__(self):
2929 if self.comment_id:
2929 if self.comment_id:
2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2931 else:
2931 else:
2932 return '<DB:ChangesetComment at %#x>' % id(self)
2932 return '<DB:ChangesetComment at %#x>' % id(self)
2933
2933
2934
2934
2935 class ChangesetStatus(Base, BaseModel):
2935 class ChangesetStatus(Base, BaseModel):
2936 __tablename__ = 'changeset_statuses'
2936 __tablename__ = 'changeset_statuses'
2937 __table_args__ = (
2937 __table_args__ = (
2938 Index('cs_revision_idx', 'revision'),
2938 Index('cs_revision_idx', 'revision'),
2939 Index('cs_version_idx', 'version'),
2939 Index('cs_version_idx', 'version'),
2940 UniqueConstraint('repo_id', 'revision', 'version'),
2940 UniqueConstraint('repo_id', 'revision', 'version'),
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 )
2943 )
2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2945 STATUS_APPROVED = 'approved'
2945 STATUS_APPROVED = 'approved'
2946 STATUS_REJECTED = 'rejected'
2946 STATUS_REJECTED = 'rejected'
2947 STATUS_UNDER_REVIEW = 'under_review'
2947 STATUS_UNDER_REVIEW = 'under_review'
2948
2948
2949 STATUSES = [
2949 STATUSES = [
2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2951 (STATUS_APPROVED, _("Approved")),
2951 (STATUS_APPROVED, _("Approved")),
2952 (STATUS_REJECTED, _("Rejected")),
2952 (STATUS_REJECTED, _("Rejected")),
2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2954 ]
2954 ]
2955
2955
2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2959 revision = Column('revision', String(40), nullable=False)
2959 revision = Column('revision', String(40), nullable=False)
2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2963 version = Column('version', Integer(), nullable=False, default=0)
2963 version = Column('version', Integer(), nullable=False, default=0)
2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2965
2965
2966 author = relationship('User', lazy='joined')
2966 author = relationship('User', lazy='joined')
2967 repo = relationship('Repository')
2967 repo = relationship('Repository')
2968 comment = relationship('ChangesetComment', lazy='joined')
2968 comment = relationship('ChangesetComment', lazy='joined')
2969 pull_request = relationship('PullRequest', lazy='joined')
2969 pull_request = relationship('PullRequest', lazy='joined')
2970
2970
2971 def __unicode__(self):
2971 def __unicode__(self):
2972 return u"<%s('%s[%s]:%s')>" % (
2972 return u"<%s('%s[%s]:%s')>" % (
2973 self.__class__.__name__,
2973 self.__class__.__name__,
2974 self.status, self.version, self.author
2974 self.status, self.version, self.author
2975 )
2975 )
2976
2976
2977 @classmethod
2977 @classmethod
2978 def get_status_lbl(cls, value):
2978 def get_status_lbl(cls, value):
2979 return dict(cls.STATUSES).get(value)
2979 return dict(cls.STATUSES).get(value)
2980
2980
2981 @property
2981 @property
2982 def status_lbl(self):
2982 def status_lbl(self):
2983 return ChangesetStatus.get_status_lbl(self.status)
2983 return ChangesetStatus.get_status_lbl(self.status)
2984
2984
2985
2985
2986 class _PullRequestBase(BaseModel):
2986 class _PullRequestBase(BaseModel):
2987 """
2987 """
2988 Common attributes of pull request and version entries.
2988 Common attributes of pull request and version entries.
2989 """
2989 """
2990
2990
2991 # .status values
2991 # .status values
2992 STATUS_NEW = u'new'
2992 STATUS_NEW = u'new'
2993 STATUS_OPEN = u'open'
2993 STATUS_OPEN = u'open'
2994 STATUS_CLOSED = u'closed'
2994 STATUS_CLOSED = u'closed'
2995
2995
2996 title = Column('title', Unicode(255), nullable=True)
2996 title = Column('title', Unicode(255), nullable=True)
2997 description = Column(
2997 description = Column(
2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2999 nullable=True)
2999 nullable=True)
3000 # new/open/closed status of pull request (not approve/reject/etc)
3000 # new/open/closed status of pull request (not approve/reject/etc)
3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3002 created_on = Column(
3002 created_on = Column(
3003 'created_on', DateTime(timezone=False), nullable=False,
3003 'created_on', DateTime(timezone=False), nullable=False,
3004 default=datetime.datetime.now)
3004 default=datetime.datetime.now)
3005 updated_on = Column(
3005 updated_on = Column(
3006 'updated_on', DateTime(timezone=False), nullable=False,
3006 'updated_on', DateTime(timezone=False), nullable=False,
3007 default=datetime.datetime.now)
3007 default=datetime.datetime.now)
3008
3008
3009 @declared_attr
3009 @declared_attr
3010 def user_id(cls):
3010 def user_id(cls):
3011 return Column(
3011 return Column(
3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3013 unique=None)
3013 unique=None)
3014
3014
3015 # 500 revisions max
3015 # 500 revisions max
3016 _revisions = Column(
3016 _revisions = Column(
3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3018
3018
3019 @declared_attr
3019 @declared_attr
3020 def source_repo_id(cls):
3020 def source_repo_id(cls):
3021 # TODO: dan: rename column to source_repo_id
3021 # TODO: dan: rename column to source_repo_id
3022 return Column(
3022 return Column(
3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3024 nullable=False)
3024 nullable=False)
3025
3025
3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3027
3027
3028 @declared_attr
3028 @declared_attr
3029 def target_repo_id(cls):
3029 def target_repo_id(cls):
3030 # TODO: dan: rename column to target_repo_id
3030 # TODO: dan: rename column to target_repo_id
3031 return Column(
3031 return Column(
3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3033 nullable=False)
3033 nullable=False)
3034
3034
3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3036
3036
3037 # TODO: dan: rename column to last_merge_source_rev
3037 # TODO: dan: rename column to last_merge_source_rev
3038 _last_merge_source_rev = Column(
3038 _last_merge_source_rev = Column(
3039 'last_merge_org_rev', String(40), nullable=True)
3039 'last_merge_org_rev', String(40), nullable=True)
3040 # TODO: dan: rename column to last_merge_target_rev
3040 # TODO: dan: rename column to last_merge_target_rev
3041 _last_merge_target_rev = Column(
3041 _last_merge_target_rev = Column(
3042 'last_merge_other_rev', String(40), nullable=True)
3042 'last_merge_other_rev', String(40), nullable=True)
3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3045
3045
3046 @hybrid_property
3046 @hybrid_property
3047 def revisions(self):
3047 def revisions(self):
3048 return self._revisions.split(':') if self._revisions else []
3048 return self._revisions.split(':') if self._revisions else []
3049
3049
3050 @revisions.setter
3050 @revisions.setter
3051 def revisions(self, val):
3051 def revisions(self, val):
3052 self._revisions = ':'.join(val)
3052 self._revisions = ':'.join(val)
3053
3053
3054 @declared_attr
3054 @declared_attr
3055 def author(cls):
3055 def author(cls):
3056 return relationship('User', lazy='joined')
3056 return relationship('User', lazy='joined')
3057
3057
3058 @declared_attr
3058 @declared_attr
3059 def source_repo(cls):
3059 def source_repo(cls):
3060 return relationship(
3060 return relationship(
3061 'Repository',
3061 'Repository',
3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3063
3063
3064 @property
3064 @property
3065 def source_ref_parts(self):
3065 def source_ref_parts(self):
3066 refs = self.source_ref.split(':')
3066 refs = self.source_ref.split(':')
3067 return Reference(refs[0], refs[1], refs[2])
3067 return Reference(refs[0], refs[1], refs[2])
3068
3068
3069 @declared_attr
3069 @declared_attr
3070 def target_repo(cls):
3070 def target_repo(cls):
3071 return relationship(
3071 return relationship(
3072 'Repository',
3072 'Repository',
3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3074
3074
3075 @property
3075 @property
3076 def target_ref_parts(self):
3076 def target_ref_parts(self):
3077 refs = self.target_ref.split(':')
3077 refs = self.target_ref.split(':')
3078 return Reference(refs[0], refs[1], refs[2])
3078 return Reference(refs[0], refs[1], refs[2])
3079
3079
3080
3080
3081 class PullRequest(Base, _PullRequestBase):
3081 class PullRequest(Base, _PullRequestBase):
3082 __tablename__ = 'pull_requests'
3082 __tablename__ = 'pull_requests'
3083 __table_args__ = (
3083 __table_args__ = (
3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3086 )
3086 )
3087
3087
3088 pull_request_id = Column(
3088 pull_request_id = Column(
3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3090
3090
3091 def __repr__(self):
3091 def __repr__(self):
3092 if self.pull_request_id:
3092 if self.pull_request_id:
3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3094 else:
3094 else:
3095 return '<DB:PullRequest at %#x>' % id(self)
3095 return '<DB:PullRequest at %#x>' % id(self)
3096
3096
3097 reviewers = relationship('PullRequestReviewers',
3097 reviewers = relationship('PullRequestReviewers',
3098 cascade="all, delete, delete-orphan")
3098 cascade="all, delete, delete-orphan")
3099 statuses = relationship('ChangesetStatus')
3099 statuses = relationship('ChangesetStatus')
3100 comments = relationship('ChangesetComment',
3100 comments = relationship('ChangesetComment',
3101 cascade="all, delete, delete-orphan")
3101 cascade="all, delete, delete-orphan")
3102 versions = relationship('PullRequestVersion',
3102 versions = relationship('PullRequestVersion',
3103 cascade="all, delete, delete-orphan")
3103 cascade="all, delete, delete-orphan")
3104
3104
3105 def is_closed(self):
3105 def is_closed(self):
3106 return self.status == self.STATUS_CLOSED
3106 return self.status == self.STATUS_CLOSED
3107
3107
3108 def get_api_data(self):
3108 def get_api_data(self):
3109 from rhodecode.model.pull_request import PullRequestModel
3109 from rhodecode.model.pull_request import PullRequestModel
3110 pull_request = self
3110 pull_request = self
3111 merge_status = PullRequestModel().merge_status(pull_request)
3111 merge_status = PullRequestModel().merge_status(pull_request)
3112 pull_request_url = url(
3113 'pullrequest_show', repo_name=self.target_repo.repo_name,
3114 pull_request_id=self.pull_request_id, qualified=True)
3112 data = {
3115 data = {
3113 'pull_request_id': pull_request.pull_request_id,
3116 'pull_request_id': pull_request.pull_request_id,
3114 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3117 'url': pull_request_url,
3115 pull_request_id=self.pull_request_id,
3116 qualified=True),
3117 'title': pull_request.title,
3118 'title': pull_request.title,
3118 'description': pull_request.description,
3119 'description': pull_request.description,
3119 'status': pull_request.status,
3120 'status': pull_request.status,
3120 'created_on': pull_request.created_on,
3121 'created_on': pull_request.created_on,
3121 'updated_on': pull_request.updated_on,
3122 'updated_on': pull_request.updated_on,
3122 'commit_ids': pull_request.revisions,
3123 'commit_ids': pull_request.revisions,
3123 'review_status': pull_request.calculated_review_status(),
3124 'review_status': pull_request.calculated_review_status(),
3124 'mergeable': {
3125 'mergeable': {
3125 'status': merge_status[0],
3126 'status': merge_status[0],
3126 'message': unicode(merge_status[1]),
3127 'message': unicode(merge_status[1]),
3127 },
3128 },
3128 'source': {
3129 'source': {
3129 'clone_url': pull_request.source_repo.clone_url(),
3130 'clone_url': pull_request.source_repo.clone_url(),
3130 'repository': pull_request.source_repo.repo_name,
3131 'repository': pull_request.source_repo.repo_name,
3131 'reference': {
3132 'reference': {
3132 'name': pull_request.source_ref_parts.name,
3133 'name': pull_request.source_ref_parts.name,
3133 'type': pull_request.source_ref_parts.type,
3134 'type': pull_request.source_ref_parts.type,
3134 'commit_id': pull_request.source_ref_parts.commit_id,
3135 'commit_id': pull_request.source_ref_parts.commit_id,
3135 },
3136 },
3136 },
3137 },
3137 'target': {
3138 'target': {
3138 'clone_url': pull_request.target_repo.clone_url(),
3139 'clone_url': pull_request.target_repo.clone_url(),
3139 'repository': pull_request.target_repo.repo_name,
3140 'repository': pull_request.target_repo.repo_name,
3140 'reference': {
3141 'reference': {
3141 'name': pull_request.target_ref_parts.name,
3142 'name': pull_request.target_ref_parts.name,
3142 'type': pull_request.target_ref_parts.type,
3143 'type': pull_request.target_ref_parts.type,
3143 'commit_id': pull_request.target_ref_parts.commit_id,
3144 'commit_id': pull_request.target_ref_parts.commit_id,
3144 },
3145 },
3145 },
3146 },
3147 'shadow': {
3148 # TODO: martinb: Unify generation/suffix of clone url.
3149 'clone_url': '{}/repository'.format(pull_request_url),
3150 },
3146 'author': pull_request.author.get_api_data(include_secrets=False,
3151 'author': pull_request.author.get_api_data(include_secrets=False,
3147 details='basic'),
3152 details='basic'),
3148 'reviewers': [
3153 'reviewers': [
3149 {
3154 {
3150 'user': reviewer.get_api_data(include_secrets=False,
3155 'user': reviewer.get_api_data(include_secrets=False,
3151 details='basic'),
3156 details='basic'),
3152 'reasons': reasons,
3157 'reasons': reasons,
3153 'review_status': st[0][1].status if st else 'not_reviewed',
3158 'review_status': st[0][1].status if st else 'not_reviewed',
3154 }
3159 }
3155 for reviewer, reasons, st in pull_request.reviewers_statuses()
3160 for reviewer, reasons, st in pull_request.reviewers_statuses()
3156 ]
3161 ]
3157 }
3162 }
3158
3163
3159 return data
3164 return data
3160
3165
3161 def __json__(self):
3166 def __json__(self):
3162 return {
3167 return {
3163 'revisions': self.revisions,
3168 'revisions': self.revisions,
3164 }
3169 }
3165
3170
3166 def calculated_review_status(self):
3171 def calculated_review_status(self):
3167 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3172 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3168 # because it's tricky on how to use ChangesetStatusModel from there
3173 # because it's tricky on how to use ChangesetStatusModel from there
3169 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3174 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3170 from rhodecode.model.changeset_status import ChangesetStatusModel
3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3171 return ChangesetStatusModel().calculated_review_status(self)
3176 return ChangesetStatusModel().calculated_review_status(self)
3172
3177
3173 def reviewers_statuses(self):
3178 def reviewers_statuses(self):
3174 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3179 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3180 from rhodecode.model.changeset_status import ChangesetStatusModel
3176 return ChangesetStatusModel().reviewers_statuses(self)
3181 return ChangesetStatusModel().reviewers_statuses(self)
3177
3182
3178
3183
3179 class PullRequestVersion(Base, _PullRequestBase):
3184 class PullRequestVersion(Base, _PullRequestBase):
3180 __tablename__ = 'pull_request_versions'
3185 __tablename__ = 'pull_request_versions'
3181 __table_args__ = (
3186 __table_args__ = (
3182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3184 )
3189 )
3185
3190
3186 pull_request_version_id = Column(
3191 pull_request_version_id = Column(
3187 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3192 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3188 pull_request_id = Column(
3193 pull_request_id = Column(
3189 'pull_request_id', Integer(),
3194 'pull_request_id', Integer(),
3190 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3195 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3191 pull_request = relationship('PullRequest')
3196 pull_request = relationship('PullRequest')
3192
3197
3193 def __repr__(self):
3198 def __repr__(self):
3194 if self.pull_request_version_id:
3199 if self.pull_request_version_id:
3195 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3200 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3196 else:
3201 else:
3197 return '<DB:PullRequestVersion at %#x>' % id(self)
3202 return '<DB:PullRequestVersion at %#x>' % id(self)
3198
3203
3199
3204
3200 class PullRequestReviewers(Base, BaseModel):
3205 class PullRequestReviewers(Base, BaseModel):
3201 __tablename__ = 'pull_request_reviewers'
3206 __tablename__ = 'pull_request_reviewers'
3202 __table_args__ = (
3207 __table_args__ = (
3203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3208 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3209 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3205 )
3210 )
3206
3211
3207 def __init__(self, user=None, pull_request=None, reasons=None):
3212 def __init__(self, user=None, pull_request=None, reasons=None):
3208 self.user = user
3213 self.user = user
3209 self.pull_request = pull_request
3214 self.pull_request = pull_request
3210 self.reasons = reasons or []
3215 self.reasons = reasons or []
3211
3216
3212 @hybrid_property
3217 @hybrid_property
3213 def reasons(self):
3218 def reasons(self):
3214 if not self._reasons:
3219 if not self._reasons:
3215 return []
3220 return []
3216 return self._reasons
3221 return self._reasons
3217
3222
3218 @reasons.setter
3223 @reasons.setter
3219 def reasons(self, val):
3224 def reasons(self, val):
3220 val = val or []
3225 val = val or []
3221 if any(not isinstance(x, basestring) for x in val):
3226 if any(not isinstance(x, basestring) for x in val):
3222 raise Exception('invalid reasons type, must be list of strings')
3227 raise Exception('invalid reasons type, must be list of strings')
3223 self._reasons = val
3228 self._reasons = val
3224
3229
3225 pull_requests_reviewers_id = Column(
3230 pull_requests_reviewers_id = Column(
3226 'pull_requests_reviewers_id', Integer(), nullable=False,
3231 'pull_requests_reviewers_id', Integer(), nullable=False,
3227 primary_key=True)
3232 primary_key=True)
3228 pull_request_id = Column(
3233 pull_request_id = Column(
3229 "pull_request_id", Integer(),
3234 "pull_request_id", Integer(),
3230 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3235 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3231 user_id = Column(
3236 user_id = Column(
3232 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3237 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3233 _reasons = Column(
3238 _reasons = Column(
3234 'reason', MutationList.as_mutable(
3239 'reason', MutationList.as_mutable(
3235 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3240 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3236
3241
3237 user = relationship('User')
3242 user = relationship('User')
3238 pull_request = relationship('PullRequest')
3243 pull_request = relationship('PullRequest')
3239
3244
3240
3245
3241 class Notification(Base, BaseModel):
3246 class Notification(Base, BaseModel):
3242 __tablename__ = 'notifications'
3247 __tablename__ = 'notifications'
3243 __table_args__ = (
3248 __table_args__ = (
3244 Index('notification_type_idx', 'type'),
3249 Index('notification_type_idx', 'type'),
3245 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3246 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3251 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3247 )
3252 )
3248
3253
3249 TYPE_CHANGESET_COMMENT = u'cs_comment'
3254 TYPE_CHANGESET_COMMENT = u'cs_comment'
3250 TYPE_MESSAGE = u'message'
3255 TYPE_MESSAGE = u'message'
3251 TYPE_MENTION = u'mention'
3256 TYPE_MENTION = u'mention'
3252 TYPE_REGISTRATION = u'registration'
3257 TYPE_REGISTRATION = u'registration'
3253 TYPE_PULL_REQUEST = u'pull_request'
3258 TYPE_PULL_REQUEST = u'pull_request'
3254 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3259 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3255
3260
3256 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3261 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3257 subject = Column('subject', Unicode(512), nullable=True)
3262 subject = Column('subject', Unicode(512), nullable=True)
3258 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3263 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3259 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3264 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3261 type_ = Column('type', Unicode(255))
3266 type_ = Column('type', Unicode(255))
3262
3267
3263 created_by_user = relationship('User')
3268 created_by_user = relationship('User')
3264 notifications_to_users = relationship('UserNotification', lazy='joined',
3269 notifications_to_users = relationship('UserNotification', lazy='joined',
3265 cascade="all, delete, delete-orphan")
3270 cascade="all, delete, delete-orphan")
3266
3271
3267 @property
3272 @property
3268 def recipients(self):
3273 def recipients(self):
3269 return [x.user for x in UserNotification.query()\
3274 return [x.user for x in UserNotification.query()\
3270 .filter(UserNotification.notification == self)\
3275 .filter(UserNotification.notification == self)\
3271 .order_by(UserNotification.user_id.asc()).all()]
3276 .order_by(UserNotification.user_id.asc()).all()]
3272
3277
3273 @classmethod
3278 @classmethod
3274 def create(cls, created_by, subject, body, recipients, type_=None):
3279 def create(cls, created_by, subject, body, recipients, type_=None):
3275 if type_ is None:
3280 if type_ is None:
3276 type_ = Notification.TYPE_MESSAGE
3281 type_ = Notification.TYPE_MESSAGE
3277
3282
3278 notification = cls()
3283 notification = cls()
3279 notification.created_by_user = created_by
3284 notification.created_by_user = created_by
3280 notification.subject = subject
3285 notification.subject = subject
3281 notification.body = body
3286 notification.body = body
3282 notification.type_ = type_
3287 notification.type_ = type_
3283 notification.created_on = datetime.datetime.now()
3288 notification.created_on = datetime.datetime.now()
3284
3289
3285 for u in recipients:
3290 for u in recipients:
3286 assoc = UserNotification()
3291 assoc = UserNotification()
3287 assoc.notification = notification
3292 assoc.notification = notification
3288
3293
3289 # if created_by is inside recipients mark his notification
3294 # if created_by is inside recipients mark his notification
3290 # as read
3295 # as read
3291 if u.user_id == created_by.user_id:
3296 if u.user_id == created_by.user_id:
3292 assoc.read = True
3297 assoc.read = True
3293
3298
3294 u.notifications.append(assoc)
3299 u.notifications.append(assoc)
3295 Session().add(notification)
3300 Session().add(notification)
3296
3301
3297 return notification
3302 return notification
3298
3303
3299 @property
3304 @property
3300 def description(self):
3305 def description(self):
3301 from rhodecode.model.notification import NotificationModel
3306 from rhodecode.model.notification import NotificationModel
3302 return NotificationModel().make_description(self)
3307 return NotificationModel().make_description(self)
3303
3308
3304
3309
3305 class UserNotification(Base, BaseModel):
3310 class UserNotification(Base, BaseModel):
3306 __tablename__ = 'user_to_notification'
3311 __tablename__ = 'user_to_notification'
3307 __table_args__ = (
3312 __table_args__ = (
3308 UniqueConstraint('user_id', 'notification_id'),
3313 UniqueConstraint('user_id', 'notification_id'),
3309 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3310 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3315 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3311 )
3316 )
3312 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3317 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3313 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3318 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3314 read = Column('read', Boolean, default=False)
3319 read = Column('read', Boolean, default=False)
3315 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3320 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3316
3321
3317 user = relationship('User', lazy="joined")
3322 user = relationship('User', lazy="joined")
3318 notification = relationship('Notification', lazy="joined",
3323 notification = relationship('Notification', lazy="joined",
3319 order_by=lambda: Notification.created_on.desc(),)
3324 order_by=lambda: Notification.created_on.desc(),)
3320
3325
3321 def mark_as_read(self):
3326 def mark_as_read(self):
3322 self.read = True
3327 self.read = True
3323 Session().add(self)
3328 Session().add(self)
3324
3329
3325
3330
3326 class Gist(Base, BaseModel):
3331 class Gist(Base, BaseModel):
3327 __tablename__ = 'gists'
3332 __tablename__ = 'gists'
3328 __table_args__ = (
3333 __table_args__ = (
3329 Index('g_gist_access_id_idx', 'gist_access_id'),
3334 Index('g_gist_access_id_idx', 'gist_access_id'),
3330 Index('g_created_on_idx', 'created_on'),
3335 Index('g_created_on_idx', 'created_on'),
3331 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3336 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3332 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3337 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3333 )
3338 )
3334 GIST_PUBLIC = u'public'
3339 GIST_PUBLIC = u'public'
3335 GIST_PRIVATE = u'private'
3340 GIST_PRIVATE = u'private'
3336 DEFAULT_FILENAME = u'gistfile1.txt'
3341 DEFAULT_FILENAME = u'gistfile1.txt'
3337
3342
3338 ACL_LEVEL_PUBLIC = u'acl_public'
3343 ACL_LEVEL_PUBLIC = u'acl_public'
3339 ACL_LEVEL_PRIVATE = u'acl_private'
3344 ACL_LEVEL_PRIVATE = u'acl_private'
3340
3345
3341 gist_id = Column('gist_id', Integer(), primary_key=True)
3346 gist_id = Column('gist_id', Integer(), primary_key=True)
3342 gist_access_id = Column('gist_access_id', Unicode(250))
3347 gist_access_id = Column('gist_access_id', Unicode(250))
3343 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3348 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3344 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3349 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3345 gist_expires = Column('gist_expires', Float(53), nullable=False)
3350 gist_expires = Column('gist_expires', Float(53), nullable=False)
3346 gist_type = Column('gist_type', Unicode(128), nullable=False)
3351 gist_type = Column('gist_type', Unicode(128), nullable=False)
3347 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3352 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3348 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3353 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3349 acl_level = Column('acl_level', Unicode(128), nullable=True)
3354 acl_level = Column('acl_level', Unicode(128), nullable=True)
3350
3355
3351 owner = relationship('User')
3356 owner = relationship('User')
3352
3357
3353 def __repr__(self):
3358 def __repr__(self):
3354 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3359 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3355
3360
3356 @classmethod
3361 @classmethod
3357 def get_or_404(cls, id_):
3362 def get_or_404(cls, id_):
3358 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3363 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3359 if not res:
3364 if not res:
3360 raise HTTPNotFound
3365 raise HTTPNotFound
3361 return res
3366 return res
3362
3367
3363 @classmethod
3368 @classmethod
3364 def get_by_access_id(cls, gist_access_id):
3369 def get_by_access_id(cls, gist_access_id):
3365 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3370 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3366
3371
3367 def gist_url(self):
3372 def gist_url(self):
3368 import rhodecode
3373 import rhodecode
3369 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3374 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3370 if alias_url:
3375 if alias_url:
3371 return alias_url.replace('{gistid}', self.gist_access_id)
3376 return alias_url.replace('{gistid}', self.gist_access_id)
3372
3377
3373 return url('gist', gist_id=self.gist_access_id, qualified=True)
3378 return url('gist', gist_id=self.gist_access_id, qualified=True)
3374
3379
3375 @classmethod
3380 @classmethod
3376 def base_path(cls):
3381 def base_path(cls):
3377 """
3382 """
3378 Returns base path when all gists are stored
3383 Returns base path when all gists are stored
3379
3384
3380 :param cls:
3385 :param cls:
3381 """
3386 """
3382 from rhodecode.model.gist import GIST_STORE_LOC
3387 from rhodecode.model.gist import GIST_STORE_LOC
3383 q = Session().query(RhodeCodeUi)\
3388 q = Session().query(RhodeCodeUi)\
3384 .filter(RhodeCodeUi.ui_key == URL_SEP)
3389 .filter(RhodeCodeUi.ui_key == URL_SEP)
3385 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3390 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3386 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3391 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3387
3392
3388 def get_api_data(self):
3393 def get_api_data(self):
3389 """
3394 """
3390 Common function for generating gist related data for API
3395 Common function for generating gist related data for API
3391 """
3396 """
3392 gist = self
3397 gist = self
3393 data = {
3398 data = {
3394 'gist_id': gist.gist_id,
3399 'gist_id': gist.gist_id,
3395 'type': gist.gist_type,
3400 'type': gist.gist_type,
3396 'access_id': gist.gist_access_id,
3401 'access_id': gist.gist_access_id,
3397 'description': gist.gist_description,
3402 'description': gist.gist_description,
3398 'url': gist.gist_url(),
3403 'url': gist.gist_url(),
3399 'expires': gist.gist_expires,
3404 'expires': gist.gist_expires,
3400 'created_on': gist.created_on,
3405 'created_on': gist.created_on,
3401 'modified_at': gist.modified_at,
3406 'modified_at': gist.modified_at,
3402 'content': None,
3407 'content': None,
3403 'acl_level': gist.acl_level,
3408 'acl_level': gist.acl_level,
3404 }
3409 }
3405 return data
3410 return data
3406
3411
3407 def __json__(self):
3412 def __json__(self):
3408 data = dict(
3413 data = dict(
3409 )
3414 )
3410 data.update(self.get_api_data())
3415 data.update(self.get_api_data())
3411 return data
3416 return data
3412 # SCM functions
3417 # SCM functions
3413
3418
3414 def scm_instance(self, **kwargs):
3419 def scm_instance(self, **kwargs):
3415 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3420 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3416 return get_vcs_instance(
3421 return get_vcs_instance(
3417 repo_path=safe_str(full_repo_path), create=False)
3422 repo_path=safe_str(full_repo_path), create=False)
3418
3423
3419
3424
3420 class DbMigrateVersion(Base, BaseModel):
3425 class DbMigrateVersion(Base, BaseModel):
3421 __tablename__ = 'db_migrate_version'
3426 __tablename__ = 'db_migrate_version'
3422 __table_args__ = (
3427 __table_args__ = (
3423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3428 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3424 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3429 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3425 )
3430 )
3426 repository_id = Column('repository_id', String(250), primary_key=True)
3431 repository_id = Column('repository_id', String(250), primary_key=True)
3427 repository_path = Column('repository_path', Text)
3432 repository_path = Column('repository_path', Text)
3428 version = Column('version', Integer)
3433 version = Column('version', Integer)
3429
3434
3430
3435
3431 class ExternalIdentity(Base, BaseModel):
3436 class ExternalIdentity(Base, BaseModel):
3432 __tablename__ = 'external_identities'
3437 __tablename__ = 'external_identities'
3433 __table_args__ = (
3438 __table_args__ = (
3434 Index('local_user_id_idx', 'local_user_id'),
3439 Index('local_user_id_idx', 'local_user_id'),
3435 Index('external_id_idx', 'external_id'),
3440 Index('external_id_idx', 'external_id'),
3436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3441 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3437 'mysql_charset': 'utf8'})
3442 'mysql_charset': 'utf8'})
3438
3443
3439 external_id = Column('external_id', Unicode(255), default=u'',
3444 external_id = Column('external_id', Unicode(255), default=u'',
3440 primary_key=True)
3445 primary_key=True)
3441 external_username = Column('external_username', Unicode(1024), default=u'')
3446 external_username = Column('external_username', Unicode(1024), default=u'')
3442 local_user_id = Column('local_user_id', Integer(),
3447 local_user_id = Column('local_user_id', Integer(),
3443 ForeignKey('users.user_id'), primary_key=True)
3448 ForeignKey('users.user_id'), primary_key=True)
3444 provider_name = Column('provider_name', Unicode(255), default=u'',
3449 provider_name = Column('provider_name', Unicode(255), default=u'',
3445 primary_key=True)
3450 primary_key=True)
3446 access_token = Column('access_token', String(1024), default=u'')
3451 access_token = Column('access_token', String(1024), default=u'')
3447 alt_token = Column('alt_token', String(1024), default=u'')
3452 alt_token = Column('alt_token', String(1024), default=u'')
3448 token_secret = Column('token_secret', String(1024), default=u'')
3453 token_secret = Column('token_secret', String(1024), default=u'')
3449
3454
3450 @classmethod
3455 @classmethod
3451 def by_external_id_and_provider(cls, external_id, provider_name,
3456 def by_external_id_and_provider(cls, external_id, provider_name,
3452 local_user_id=None):
3457 local_user_id=None):
3453 """
3458 """
3454 Returns ExternalIdentity instance based on search params
3459 Returns ExternalIdentity instance based on search params
3455
3460
3456 :param external_id:
3461 :param external_id:
3457 :param provider_name:
3462 :param provider_name:
3458 :return: ExternalIdentity
3463 :return: ExternalIdentity
3459 """
3464 """
3460 query = cls.query()
3465 query = cls.query()
3461 query = query.filter(cls.external_id == external_id)
3466 query = query.filter(cls.external_id == external_id)
3462 query = query.filter(cls.provider_name == provider_name)
3467 query = query.filter(cls.provider_name == provider_name)
3463 if local_user_id:
3468 if local_user_id:
3464 query = query.filter(cls.local_user_id == local_user_id)
3469 query = query.filter(cls.local_user_id == local_user_id)
3465 return query.first()
3470 return query.first()
3466
3471
3467 @classmethod
3472 @classmethod
3468 def user_by_external_id_and_provider(cls, external_id, provider_name):
3473 def user_by_external_id_and_provider(cls, external_id, provider_name):
3469 """
3474 """
3470 Returns User instance based on search params
3475 Returns User instance based on search params
3471
3476
3472 :param external_id:
3477 :param external_id:
3473 :param provider_name:
3478 :param provider_name:
3474 :return: User
3479 :return: User
3475 """
3480 """
3476 query = User.query()
3481 query = User.query()
3477 query = query.filter(cls.external_id == external_id)
3482 query = query.filter(cls.external_id == external_id)
3478 query = query.filter(cls.provider_name == provider_name)
3483 query = query.filter(cls.provider_name == provider_name)
3479 query = query.filter(User.user_id == cls.local_user_id)
3484 query = query.filter(User.user_id == cls.local_user_id)
3480 return query.first()
3485 return query.first()
3481
3486
3482 @classmethod
3487 @classmethod
3483 def by_local_user_id(cls, local_user_id):
3488 def by_local_user_id(cls, local_user_id):
3484 """
3489 """
3485 Returns all tokens for user
3490 Returns all tokens for user
3486
3491
3487 :param local_user_id:
3492 :param local_user_id:
3488 :return: ExternalIdentity
3493 :return: ExternalIdentity
3489 """
3494 """
3490 query = cls.query()
3495 query = cls.query()
3491 query = query.filter(cls.local_user_id == local_user_id)
3496 query = query.filter(cls.local_user_id == local_user_id)
3492 return query
3497 return query
3493
3498
3494
3499
3495 class Integration(Base, BaseModel):
3500 class Integration(Base, BaseModel):
3496 __tablename__ = 'integrations'
3501 __tablename__ = 'integrations'
3497 __table_args__ = (
3502 __table_args__ = (
3498 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3503 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3499 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3504 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3500 )
3505 )
3501
3506
3502 integration_id = Column('integration_id', Integer(), primary_key=True)
3507 integration_id = Column('integration_id', Integer(), primary_key=True)
3503 integration_type = Column('integration_type', String(255))
3508 integration_type = Column('integration_type', String(255))
3504 enabled = Column('enabled', Boolean(), nullable=False)
3509 enabled = Column('enabled', Boolean(), nullable=False)
3505 name = Column('name', String(255), nullable=False)
3510 name = Column('name', String(255), nullable=False)
3506 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3511 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3507 default=False)
3512 default=False)
3508
3513
3509 settings = Column(
3514 settings = Column(
3510 'settings_json', MutationObj.as_mutable(
3515 'settings_json', MutationObj.as_mutable(
3511 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3516 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3512 repo_id = Column(
3517 repo_id = Column(
3513 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3518 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3514 nullable=True, unique=None, default=None)
3519 nullable=True, unique=None, default=None)
3515 repo = relationship('Repository', lazy='joined')
3520 repo = relationship('Repository', lazy='joined')
3516
3521
3517 repo_group_id = Column(
3522 repo_group_id = Column(
3518 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3523 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3519 nullable=True, unique=None, default=None)
3524 nullable=True, unique=None, default=None)
3520 repo_group = relationship('RepoGroup', lazy='joined')
3525 repo_group = relationship('RepoGroup', lazy='joined')
3521
3526
3522 @property
3527 @property
3523 def scope(self):
3528 def scope(self):
3524 if self.repo:
3529 if self.repo:
3525 return repr(self.repo)
3530 return repr(self.repo)
3526 if self.repo_group:
3531 if self.repo_group:
3527 if self.child_repos_only:
3532 if self.child_repos_only:
3528 return repr(self.repo_group) + ' (child repos only)'
3533 return repr(self.repo_group) + ' (child repos only)'
3529 else:
3534 else:
3530 return repr(self.repo_group) + ' (recursive)'
3535 return repr(self.repo_group) + ' (recursive)'
3531 if self.child_repos_only:
3536 if self.child_repos_only:
3532 return 'root_repos'
3537 return 'root_repos'
3533 return 'global'
3538 return 'global'
3534
3539
3535 def __repr__(self):
3540 def __repr__(self):
3536 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3541 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3537
3542
3538
3543
3539 class RepoReviewRuleUser(Base, BaseModel):
3544 class RepoReviewRuleUser(Base, BaseModel):
3540 __tablename__ = 'repo_review_rules_users'
3545 __tablename__ = 'repo_review_rules_users'
3541 __table_args__ = (
3546 __table_args__ = (
3542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3543 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3548 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3544 )
3549 )
3545 repo_review_rule_user_id = Column(
3550 repo_review_rule_user_id = Column(
3546 'repo_review_rule_user_id', Integer(), primary_key=True)
3551 'repo_review_rule_user_id', Integer(), primary_key=True)
3547 repo_review_rule_id = Column("repo_review_rule_id",
3552 repo_review_rule_id = Column("repo_review_rule_id",
3548 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3553 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3549 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3554 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3550 nullable=False)
3555 nullable=False)
3551 user = relationship('User')
3556 user = relationship('User')
3552
3557
3553
3558
3554 class RepoReviewRuleUserGroup(Base, BaseModel):
3559 class RepoReviewRuleUserGroup(Base, BaseModel):
3555 __tablename__ = 'repo_review_rules_users_groups'
3560 __tablename__ = 'repo_review_rules_users_groups'
3556 __table_args__ = (
3561 __table_args__ = (
3557 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3558 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3563 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3559 )
3564 )
3560 repo_review_rule_users_group_id = Column(
3565 repo_review_rule_users_group_id = Column(
3561 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3566 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3562 repo_review_rule_id = Column("repo_review_rule_id",
3567 repo_review_rule_id = Column("repo_review_rule_id",
3563 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3568 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3564 users_group_id = Column("users_group_id", Integer(),
3569 users_group_id = Column("users_group_id", Integer(),
3565 ForeignKey('users_groups.users_group_id'), nullable=False)
3570 ForeignKey('users_groups.users_group_id'), nullable=False)
3566 users_group = relationship('UserGroup')
3571 users_group = relationship('UserGroup')
3567
3572
3568
3573
3569 class RepoReviewRule(Base, BaseModel):
3574 class RepoReviewRule(Base, BaseModel):
3570 __tablename__ = 'repo_review_rules'
3575 __tablename__ = 'repo_review_rules'
3571 __table_args__ = (
3576 __table_args__ = (
3572 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3573 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3578 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3574 )
3579 )
3575
3580
3576 repo_review_rule_id = Column(
3581 repo_review_rule_id = Column(
3577 'repo_review_rule_id', Integer(), primary_key=True)
3582 'repo_review_rule_id', Integer(), primary_key=True)
3578 repo_id = Column(
3583 repo_id = Column(
3579 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3584 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3580 repo = relationship('Repository', backref='review_rules')
3585 repo = relationship('Repository', backref='review_rules')
3581
3586
3582 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3587 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3583 default=u'*') # glob
3588 default=u'*') # glob
3584 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3589 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3585 default=u'*') # glob
3590 default=u'*') # glob
3586
3591
3587 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3592 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3588 nullable=False, default=False)
3593 nullable=False, default=False)
3589 rule_users = relationship('RepoReviewRuleUser')
3594 rule_users = relationship('RepoReviewRuleUser')
3590 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3595 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3591
3596
3592 @hybrid_property
3597 @hybrid_property
3593 def branch_pattern(self):
3598 def branch_pattern(self):
3594 return self._branch_pattern or '*'
3599 return self._branch_pattern or '*'
3595
3600
3596 def _validate_glob(self, value):
3601 def _validate_glob(self, value):
3597 re.compile('^' + glob2re(value) + '$')
3602 re.compile('^' + glob2re(value) + '$')
3598
3603
3599 @branch_pattern.setter
3604 @branch_pattern.setter
3600 def branch_pattern(self, value):
3605 def branch_pattern(self, value):
3601 self._validate_glob(value)
3606 self._validate_glob(value)
3602 self._branch_pattern = value or '*'
3607 self._branch_pattern = value or '*'
3603
3608
3604 @hybrid_property
3609 @hybrid_property
3605 def file_pattern(self):
3610 def file_pattern(self):
3606 return self._file_pattern or '*'
3611 return self._file_pattern or '*'
3607
3612
3608 @file_pattern.setter
3613 @file_pattern.setter
3609 def file_pattern(self, value):
3614 def file_pattern(self, value):
3610 self._validate_glob(value)
3615 self._validate_glob(value)
3611 self._file_pattern = value or '*'
3616 self._file_pattern = value or '*'
3612
3617
3613 def matches(self, branch, files_changed):
3618 def matches(self, branch, files_changed):
3614 """
3619 """
3615 Check if this review rule matches a branch/files in a pull request
3620 Check if this review rule matches a branch/files in a pull request
3616
3621
3617 :param branch: branch name for the commit
3622 :param branch: branch name for the commit
3618 :param files_changed: list of file paths changed in the pull request
3623 :param files_changed: list of file paths changed in the pull request
3619 """
3624 """
3620
3625
3621 branch = branch or ''
3626 branch = branch or ''
3622 files_changed = files_changed or []
3627 files_changed = files_changed or []
3623
3628
3624 branch_matches = True
3629 branch_matches = True
3625 if branch:
3630 if branch:
3626 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3631 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3627 branch_matches = bool(branch_regex.search(branch))
3632 branch_matches = bool(branch_regex.search(branch))
3628
3633
3629 files_matches = True
3634 files_matches = True
3630 if self.file_pattern != '*':
3635 if self.file_pattern != '*':
3631 files_matches = False
3636 files_matches = False
3632 file_regex = re.compile(glob2re(self.file_pattern))
3637 file_regex = re.compile(glob2re(self.file_pattern))
3633 for filename in files_changed:
3638 for filename in files_changed:
3634 if file_regex.search(filename):
3639 if file_regex.search(filename):
3635 files_matches = True
3640 files_matches = True
3636 break
3641 break
3637
3642
3638 return branch_matches and files_matches
3643 return branch_matches and files_matches
3639
3644
3640 @property
3645 @property
3641 def review_users(self):
3646 def review_users(self):
3642 """ Returns the users which this rule applies to """
3647 """ Returns the users which this rule applies to """
3643
3648
3644 users = set()
3649 users = set()
3645 users |= set([
3650 users |= set([
3646 rule_user.user for rule_user in self.rule_users
3651 rule_user.user for rule_user in self.rule_users
3647 if rule_user.user.active])
3652 if rule_user.user.active])
3648 users |= set(
3653 users |= set(
3649 member.user
3654 member.user
3650 for rule_user_group in self.rule_user_groups
3655 for rule_user_group in self.rule_user_groups
3651 for member in rule_user_group.users_group.members
3656 for member in rule_user_group.users_group.members
3652 if member.user.active
3657 if member.user.active
3653 )
3658 )
3654 return users
3659 return users
3655
3660
3656 def __repr__(self):
3661 def __repr__(self):
3657 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3662 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3658 self.repo_review_rule_id, self.repo)
3663 self.repo_review_rule_id, self.repo)
General Comments 0
You need to be logged in to leave comments. Login now