##// END OF EJS Templates
code: unified coverage notes to # pragma: no cover
marcink -
r3282:5c2818aa default
parent child Browse files
Show More
@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 Single source for redirection links.
22 Single source for redirection links.
23
23
24 Goal of this module is to provide a single source of truth regarding external
24 Goal of this module is to provide a single source of truth regarding external
25 links. The data inside this module is used to configure the routing
25 links. The data inside this module is used to configure the routing
26 system of Enterprise and it is used also as a base to check if this data
26 system of Enterprise and it is used also as a base to check if this data
27 and our server configuration are in sync.
27 and our server configuration are in sync.
28
28
29 .. py:data:: link_config
29 .. py:data:: link_config
30
30
31 Contains the configuration for external links. Each item is supposed to be
31 Contains the configuration for external links. Each item is supposed to be
32 a `dict` like this example::
32 a `dict` like this example::
33
33
34 {"name": "url_name",
34 {"name": "url_name",
35 "target": "https://rhodecode.com/r1/enterprise/keyword/",
35 "target": "https://rhodecode.com/r1/enterprise/keyword/",
36 "external_target": "https://example.com/some-page.html",
36 "external_target": "https://example.com/some-page.html",
37 }
37 }
38
38
39 then you can retrieve the url by simply calling the URL function:
39 then you can retrieve the url by simply calling the URL function:
40
40
41 `h.route_path('url_name')`
41 `h.route_path('url_name')`
42
42
43 The redirection must be first implemented in our servers before
43 The redirection must be first implemented in our servers before
44 you can see it working.
44 you can see it working.
45 """
45 """
46 # flake8: noqa
46 # pragma: no cover
47 from __future__ import unicode_literals
47 from __future__ import unicode_literals
48
48
49 link_config = [
49 link_config = [
50 {
50 {
51 "name": "enterprise_docs",
51 "name": "enterprise_docs",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 },
54 },
55 {
55 {
56 "name": "enterprise_log_file_locations",
56 "name": "enterprise_log_file_locations",
57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
59 },
59 },
60 {
60 {
61 "name": "enterprise_issue_tracker_settings",
61 "name": "enterprise_issue_tracker_settings",
62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
64 },
64 },
65 {
65 {
66 "name": "enterprise_svn_setup",
66 "name": "enterprise_svn_setup",
67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
69 },
69 },
70 {
70 {
71 "name": "enterprise_license_convert_from_old",
71 "name": "enterprise_license_convert_from_old",
72 "target": "https://rhodecode.com/r1/enterprise/convert-license/",
72 "target": "https://rhodecode.com/r1/enterprise/convert-license/",
73 "external_target": "https://rhodecode.com/u/license-upgrade",
73 "external_target": "https://rhodecode.com/u/license-upgrade",
74 },
74 },
75 {
75 {
76 "name": "rst_help",
76 "name": "rst_help",
77 "target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
77 "target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "external_target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "external_target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 },
79 },
80 {
80 {
81 "name": "markdown_help",
81 "name": "markdown_help",
82 "target": "https://daringfireball.net/projects/markdown/syntax",
82 "target": "https://daringfireball.net/projects/markdown/syntax",
83 "external_target": "https://daringfireball.net/projects/markdown/syntax",
83 "external_target": "https://daringfireball.net/projects/markdown/syntax",
84 },
84 },
85 {
85 {
86 "name": "rhodecode_official",
86 "name": "rhodecode_official",
87 "target": "https://rhodecode.com",
87 "target": "https://rhodecode.com",
88 "external_target": "https://rhodecode.com/",
88 "external_target": "https://rhodecode.com/",
89 },
89 },
90 {
90 {
91 "name": "rhodecode_support",
91 "name": "rhodecode_support",
92 "target": "https://rhodecode.com/help/",
92 "target": "https://rhodecode.com/help/",
93 "external_target": "https://rhodecode.com/support",
93 "external_target": "https://rhodecode.com/support",
94 },
94 },
95 {
95 {
96 "name": "rhodecode_translations",
96 "name": "rhodecode_translations",
97 "target": "https://rhodecode.com/translate/enterprise",
97 "target": "https://rhodecode.com/translate/enterprise",
98 "external_target": "https://www.transifex.com/rhodecode/RhodeCode/",
98 "external_target": "https://www.transifex.com/rhodecode/RhodeCode/",
99 },
99 },
100
100
101 ]
101 ]
102
102
103
103
104 def connect_redirection_links(config):
104 def connect_redirection_links(config):
105 for link in link_config:
105 for link in link_config:
106 config.add_route(link['name'], link['target'], static=True)
106 config.add_route(link['name'], link['target'], static=True)
@@ -1,78 +1,78 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 from pyramid.threadlocal import get_current_registry
20 from pyramid.threadlocal import get_current_registry
21 from rhodecode.events.base import RhodeCodeIntegrationEvent
21 from rhodecode.events.base import RhodeCodeIntegrationEvent
22
22
23
23
24 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
25
25
26
26
27 def trigger(event, registry=None):
27 def trigger(event, registry=None):
28 """
28 """
29 Helper method to send an event. This wraps the pyramid logic to send an
29 Helper method to send an event. This wraps the pyramid logic to send an
30 event.
30 event.
31 """
31 """
32 # For the first step we are using pyramids thread locals here. If the
32 # For the first step we are using pyramids thread locals here. If the
33 # event mechanism works out as a good solution we should think about
33 # event mechanism works out as a good solution we should think about
34 # passing the registry as an argument to get rid of it.
34 # passing the registry as an argument to get rid of it.
35 event_name = event.__class__
35 event_name = event.__class__
36 log.debug('event %s sent for execution', event_name)
36 log.debug('event %s sent for execution', event_name)
37 registry = registry or get_current_registry()
37 registry = registry or get_current_registry()
38 registry.notify(event)
38 registry.notify(event)
39 log.debug('event %s triggered using registry %s', event_name, registry)
39 log.debug('event %s triggered using registry %s', event_name, registry)
40
40
41 # Send the events to integrations directly
41 # Send the events to integrations directly
42 from rhodecode.integrations import integrations_event_handler
42 from rhodecode.integrations import integrations_event_handler
43 if isinstance(event, RhodeCodeIntegrationEvent):
43 if isinstance(event, RhodeCodeIntegrationEvent):
44 integrations_event_handler(event)
44 integrations_event_handler(event)
45
45
46
46
47 from rhodecode.events.user import ( # noqa
47 from rhodecode.events.user import ( # pragma: no cover
48 UserPreCreate,
48 UserPreCreate,
49 UserPostCreate,
49 UserPostCreate,
50 UserPreUpdate,
50 UserPreUpdate,
51 UserRegistered,
51 UserRegistered,
52 UserPermissionsChange,
52 UserPermissionsChange,
53 )
53 )
54
54
55 from rhodecode.events.repo import ( # noqa
55 from rhodecode.events.repo import ( # pragma: no cover
56 RepoEvent,
56 RepoEvent,
57 RepoPreCreateEvent, RepoCreateEvent,
57 RepoPreCreateEvent, RepoCreateEvent,
58 RepoPreDeleteEvent, RepoDeleteEvent,
58 RepoPreDeleteEvent, RepoDeleteEvent,
59 RepoPrePushEvent, RepoPushEvent,
59 RepoPrePushEvent, RepoPushEvent,
60 RepoPrePullEvent, RepoPullEvent,
60 RepoPrePullEvent, RepoPullEvent,
61 )
61 )
62
62
63 from rhodecode.events.repo_group import ( # noqa
63 from rhodecode.events.repo_group import ( # pragma: no cover
64 RepoGroupEvent,
64 RepoGroupEvent,
65 RepoGroupCreateEvent,
65 RepoGroupCreateEvent,
66 RepoGroupUpdateEvent,
66 RepoGroupUpdateEvent,
67 RepoGroupDeleteEvent,
67 RepoGroupDeleteEvent,
68 )
68 )
69
69
70 from rhodecode.events.pullrequest import ( # noqa
70 from rhodecode.events.pullrequest import ( # pragma: no cover
71 PullRequestEvent,
71 PullRequestEvent,
72 PullRequestCreateEvent,
72 PullRequestCreateEvent,
73 PullRequestUpdateEvent,
73 PullRequestUpdateEvent,
74 PullRequestCommentEvent,
74 PullRequestCommentEvent,
75 PullRequestReviewEvent,
75 PullRequestReviewEvent,
76 PullRequestMergeEvent,
76 PullRequestMergeEvent,
77 PullRequestCloseEvent,
77 PullRequestCloseEvent,
78 )
78 )
@@ -1,302 +1,302 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 Celery loader, run with::
21 Celery loader, run with::
22
22
23 celery worker \
23 celery worker \
24 --beat \
24 --beat \
25 --app rhodecode.lib.celerylib.loader \
25 --app rhodecode.lib.celerylib.loader \
26 --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
26 --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
27 --loglevel DEBUG --ini=._dev/dev.ini
27 --loglevel DEBUG --ini=._dev/dev.ini
28 """
28 """
29 import os
29 import os
30 import logging
30 import logging
31 import importlib
31 import importlib
32
32
33 from celery import Celery
33 from celery import Celery
34 from celery import signals
34 from celery import signals
35 from celery import Task
35 from celery import Task
36 from celery import exceptions # noqa
36 from celery import exceptions # pragma: no cover
37 from kombu.serialization import register
37 from kombu.serialization import register
38 from pyramid.threadlocal import get_current_request
38 from pyramid.threadlocal import get_current_request
39
39
40 import rhodecode
40 import rhodecode
41
41
42 from rhodecode.lib.auth import AuthUser
42 from rhodecode.lib.auth import AuthUser
43 from rhodecode.lib.celerylib.utils import get_ini_config, parse_ini_vars
43 from rhodecode.lib.celerylib.utils import get_ini_config, parse_ini_vars
44 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.ext_json import json
45 from rhodecode.lib.pyramid_utils import bootstrap, setup_logging, prepare_request
45 from rhodecode.lib.pyramid_utils import bootstrap, setup_logging, prepare_request
46 from rhodecode.lib.utils2 import str2bool
46 from rhodecode.lib.utils2 import str2bool
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48
48
49
49
50 register('json_ext', json.dumps, json.loads,
50 register('json_ext', json.dumps, json.loads,
51 content_type='application/x-json-ext',
51 content_type='application/x-json-ext',
52 content_encoding='utf-8')
52 content_encoding='utf-8')
53
53
54 log = logging.getLogger('celery.rhodecode.loader')
54 log = logging.getLogger('celery.rhodecode.loader')
55
55
56
56
57 def add_preload_arguments(parser):
57 def add_preload_arguments(parser):
58 parser.add_argument(
58 parser.add_argument(
59 '--ini', default=None,
59 '--ini', default=None,
60 help='Path to ini configuration file.'
60 help='Path to ini configuration file.'
61 )
61 )
62 parser.add_argument(
62 parser.add_argument(
63 '--ini-var', default=None,
63 '--ini-var', default=None,
64 help='Comma separated list of key=value to pass to ini.'
64 help='Comma separated list of key=value to pass to ini.'
65 )
65 )
66
66
67
67
68 def get_logger(obj):
68 def get_logger(obj):
69 custom_log = logging.getLogger(
69 custom_log = logging.getLogger(
70 'rhodecode.task.{}'.format(obj.__class__.__name__))
70 'rhodecode.task.{}'.format(obj.__class__.__name__))
71
71
72 if rhodecode.CELERY_ENABLED:
72 if rhodecode.CELERY_ENABLED:
73 try:
73 try:
74 custom_log = obj.get_logger()
74 custom_log = obj.get_logger()
75 except Exception:
75 except Exception:
76 pass
76 pass
77
77
78 return custom_log
78 return custom_log
79
79
80
80
81 imports = ['rhodecode.lib.celerylib.tasks']
81 imports = ['rhodecode.lib.celerylib.tasks']
82
82
83 try:
83 try:
84 # try if we have EE tasks available
84 # try if we have EE tasks available
85 importlib.import_module('rc_ee')
85 importlib.import_module('rc_ee')
86 imports.append('rc_ee.lib.celerylib.tasks')
86 imports.append('rc_ee.lib.celerylib.tasks')
87 except ImportError:
87 except ImportError:
88 pass
88 pass
89
89
90
90
91 base_celery_config = {
91 base_celery_config = {
92 'result_backend': 'rpc://',
92 'result_backend': 'rpc://',
93 'result_expires': 60 * 60 * 24,
93 'result_expires': 60 * 60 * 24,
94 'result_persistent': True,
94 'result_persistent': True,
95 'imports': imports,
95 'imports': imports,
96 'worker_max_tasks_per_child': 100,
96 'worker_max_tasks_per_child': 100,
97 'accept_content': ['json_ext'],
97 'accept_content': ['json_ext'],
98 'task_serializer': 'json_ext',
98 'task_serializer': 'json_ext',
99 'result_serializer': 'json_ext',
99 'result_serializer': 'json_ext',
100 'worker_hijack_root_logger': False,
100 'worker_hijack_root_logger': False,
101 'database_table_names': {
101 'database_table_names': {
102 'task': 'beat_taskmeta',
102 'task': 'beat_taskmeta',
103 'group': 'beat_groupmeta',
103 'group': 'beat_groupmeta',
104 }
104 }
105 }
105 }
106 # init main celery app
106 # init main celery app
107 celery_app = Celery()
107 celery_app = Celery()
108 celery_app.user_options['preload'].add(add_preload_arguments)
108 celery_app.user_options['preload'].add(add_preload_arguments)
109 ini_file_glob = None
109 ini_file_glob = None
110
110
111
111
112 @signals.setup_logging.connect
112 @signals.setup_logging.connect
113 def setup_logging_callback(**kwargs):
113 def setup_logging_callback(**kwargs):
114 setup_logging(ini_file_glob)
114 setup_logging(ini_file_glob)
115
115
116
116
117 @signals.user_preload_options.connect
117 @signals.user_preload_options.connect
118 def on_preload_parsed(options, **kwargs):
118 def on_preload_parsed(options, **kwargs):
119 ini_location = options['ini']
119 ini_location = options['ini']
120 ini_vars = options['ini_var']
120 ini_vars = options['ini_var']
121 celery_app.conf['INI_PYRAMID'] = options['ini']
121 celery_app.conf['INI_PYRAMID'] = options['ini']
122
122
123 if ini_location is None:
123 if ini_location is None:
124 print('You must provide the paste --ini argument')
124 print('You must provide the paste --ini argument')
125 exit(-1)
125 exit(-1)
126
126
127 options = None
127 options = None
128 if ini_vars is not None:
128 if ini_vars is not None:
129 options = parse_ini_vars(ini_vars)
129 options = parse_ini_vars(ini_vars)
130
130
131 global ini_file_glob
131 global ini_file_glob
132 ini_file_glob = ini_location
132 ini_file_glob = ini_location
133
133
134 log.debug('Bootstrapping RhodeCode application...')
134 log.debug('Bootstrapping RhodeCode application...')
135 env = bootstrap(ini_location, options=options)
135 env = bootstrap(ini_location, options=options)
136
136
137 setup_celery_app(
137 setup_celery_app(
138 app=env['app'], root=env['root'], request=env['request'],
138 app=env['app'], root=env['root'], request=env['request'],
139 registry=env['registry'], closer=env['closer'],
139 registry=env['registry'], closer=env['closer'],
140 ini_location=ini_location)
140 ini_location=ini_location)
141
141
142 # fix the global flag even if it's disabled via .ini file because this
142 # fix the global flag even if it's disabled via .ini file because this
143 # is a worker code that doesn't need this to be disabled.
143 # is a worker code that doesn't need this to be disabled.
144 rhodecode.CELERY_ENABLED = True
144 rhodecode.CELERY_ENABLED = True
145
145
146
146
147 @signals.task_success.connect
147 @signals.task_success.connect
148 def task_success_signal(result, **kwargs):
148 def task_success_signal(result, **kwargs):
149 meta.Session.commit()
149 meta.Session.commit()
150 closer = celery_app.conf['PYRAMID_CLOSER']
150 closer = celery_app.conf['PYRAMID_CLOSER']
151 if closer:
151 if closer:
152 closer()
152 closer()
153
153
154
154
155 @signals.task_retry.connect
155 @signals.task_retry.connect
156 def task_retry_signal(
156 def task_retry_signal(
157 request, reason, einfo, **kwargs):
157 request, reason, einfo, **kwargs):
158 meta.Session.remove()
158 meta.Session.remove()
159 closer = celery_app.conf['PYRAMID_CLOSER']
159 closer = celery_app.conf['PYRAMID_CLOSER']
160 if closer:
160 if closer:
161 closer()
161 closer()
162
162
163
163
164 @signals.task_failure.connect
164 @signals.task_failure.connect
165 def task_failure_signal(
165 def task_failure_signal(
166 task_id, exception, args, kwargs, traceback, einfo, **kargs):
166 task_id, exception, args, kwargs, traceback, einfo, **kargs):
167 from rhodecode.lib.exc_tracking import store_exception
167 from rhodecode.lib.exc_tracking import store_exception
168
168
169 meta.Session.remove()
169 meta.Session.remove()
170
170
171 # simulate sys.exc_info()
171 # simulate sys.exc_info()
172 exc_info = (einfo.type, einfo.exception, einfo.tb)
172 exc_info = (einfo.type, einfo.exception, einfo.tb)
173 store_exception(id(exc_info), exc_info, prefix='celery_rhodecode')
173 store_exception(id(exc_info), exc_info, prefix='celery_rhodecode')
174
174
175 closer = celery_app.conf['PYRAMID_CLOSER']
175 closer = celery_app.conf['PYRAMID_CLOSER']
176 if closer:
176 if closer:
177 closer()
177 closer()
178
178
179
179
180 @signals.task_revoked.connect
180 @signals.task_revoked.connect
181 def task_revoked_signal(
181 def task_revoked_signal(
182 request, terminated, signum, expired, **kwargs):
182 request, terminated, signum, expired, **kwargs):
183 closer = celery_app.conf['PYRAMID_CLOSER']
183 closer = celery_app.conf['PYRAMID_CLOSER']
184 if closer:
184 if closer:
185 closer()
185 closer()
186
186
187
187
188 def setup_celery_app(app, root, request, registry, closer, ini_location):
188 def setup_celery_app(app, root, request, registry, closer, ini_location):
189 ini_dir = os.path.dirname(os.path.abspath(ini_location))
189 ini_dir = os.path.dirname(os.path.abspath(ini_location))
190 celery_config = base_celery_config
190 celery_config = base_celery_config
191 celery_config.update({
191 celery_config.update({
192 # store celerybeat scheduler db where the .ini file is
192 # store celerybeat scheduler db where the .ini file is
193 'beat_schedule_filename': os.path.join(ini_dir, 'celerybeat-schedule'),
193 'beat_schedule_filename': os.path.join(ini_dir, 'celerybeat-schedule'),
194 })
194 })
195 ini_settings = get_ini_config(ini_location)
195 ini_settings = get_ini_config(ini_location)
196 log.debug('Got custom celery conf: %s', ini_settings)
196 log.debug('Got custom celery conf: %s', ini_settings)
197
197
198 celery_config.update(ini_settings)
198 celery_config.update(ini_settings)
199 celery_app.config_from_object(celery_config)
199 celery_app.config_from_object(celery_config)
200
200
201 celery_app.conf.update({'PYRAMID_APP': app})
201 celery_app.conf.update({'PYRAMID_APP': app})
202 celery_app.conf.update({'PYRAMID_ROOT': root})
202 celery_app.conf.update({'PYRAMID_ROOT': root})
203 celery_app.conf.update({'PYRAMID_REQUEST': request})
203 celery_app.conf.update({'PYRAMID_REQUEST': request})
204 celery_app.conf.update({'PYRAMID_REGISTRY': registry})
204 celery_app.conf.update({'PYRAMID_REGISTRY': registry})
205 celery_app.conf.update({'PYRAMID_CLOSER': closer})
205 celery_app.conf.update({'PYRAMID_CLOSER': closer})
206
206
207
207
208 def configure_celery(config, ini_location):
208 def configure_celery(config, ini_location):
209 """
209 """
210 Helper that is called from our application creation logic. It gives
210 Helper that is called from our application creation logic. It gives
211 connection info into running webapp and allows execution of tasks from
211 connection info into running webapp and allows execution of tasks from
212 RhodeCode itself
212 RhodeCode itself
213 """
213 """
214 # store some globals into rhodecode
214 # store some globals into rhodecode
215 rhodecode.CELERY_ENABLED = str2bool(
215 rhodecode.CELERY_ENABLED = str2bool(
216 config.registry.settings.get('use_celery'))
216 config.registry.settings.get('use_celery'))
217 if rhodecode.CELERY_ENABLED:
217 if rhodecode.CELERY_ENABLED:
218 log.info('Configuring celery based on `%s` file', ini_location)
218 log.info('Configuring celery based on `%s` file', ini_location)
219 setup_celery_app(
219 setup_celery_app(
220 app=None, root=None, request=None, registry=config.registry,
220 app=None, root=None, request=None, registry=config.registry,
221 closer=None, ini_location=ini_location)
221 closer=None, ini_location=ini_location)
222
222
223
223
224 def maybe_prepare_env(req):
224 def maybe_prepare_env(req):
225 environ = {}
225 environ = {}
226 try:
226 try:
227 environ.update({
227 environ.update({
228 'PATH_INFO': req.environ['PATH_INFO'],
228 'PATH_INFO': req.environ['PATH_INFO'],
229 'SCRIPT_NAME': req.environ['SCRIPT_NAME'],
229 'SCRIPT_NAME': req.environ['SCRIPT_NAME'],
230 'HTTP_HOST':
230 'HTTP_HOST':
231 req.environ.get('HTTP_HOST', req.environ['SERVER_NAME']),
231 req.environ.get('HTTP_HOST', req.environ['SERVER_NAME']),
232 'SERVER_NAME': req.environ['SERVER_NAME'],
232 'SERVER_NAME': req.environ['SERVER_NAME'],
233 'SERVER_PORT': req.environ['SERVER_PORT'],
233 'SERVER_PORT': req.environ['SERVER_PORT'],
234 'wsgi.url_scheme': req.environ['wsgi.url_scheme'],
234 'wsgi.url_scheme': req.environ['wsgi.url_scheme'],
235 })
235 })
236 except Exception:
236 except Exception:
237 pass
237 pass
238
238
239 return environ
239 return environ
240
240
241
241
242 class RequestContextTask(Task):
242 class RequestContextTask(Task):
243 """
243 """
244 This is a celery task which will create a rhodecode app instance context
244 This is a celery task which will create a rhodecode app instance context
245 for the task, patch pyramid with the original request
245 for the task, patch pyramid with the original request
246 that created the task and also add the user to the context.
246 that created the task and also add the user to the context.
247 """
247 """
248
248
249 def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
249 def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
250 link=None, link_error=None, shadow=None, **options):
250 link=None, link_error=None, shadow=None, **options):
251 """ queue the job to run (we are in web request context here) """
251 """ queue the job to run (we are in web request context here) """
252
252
253 req = get_current_request()
253 req = get_current_request()
254
254
255 # web case
255 # web case
256 if hasattr(req, 'user'):
256 if hasattr(req, 'user'):
257 ip_addr = req.user.ip_addr
257 ip_addr = req.user.ip_addr
258 user_id = req.user.user_id
258 user_id = req.user.user_id
259
259
260 # api case
260 # api case
261 elif hasattr(req, 'rpc_user'):
261 elif hasattr(req, 'rpc_user'):
262 ip_addr = req.rpc_user.ip_addr
262 ip_addr = req.rpc_user.ip_addr
263 user_id = req.rpc_user.user_id
263 user_id = req.rpc_user.user_id
264 else:
264 else:
265 raise Exception(
265 raise Exception(
266 'Unable to fetch required data from request: {}. \n'
266 'Unable to fetch required data from request: {}. \n'
267 'This task is required to be executed from context of '
267 'This task is required to be executed from context of '
268 'request in a webapp'.format(repr(req)))
268 'request in a webapp'.format(repr(req)))
269
269
270 if req:
270 if req:
271 # we hook into kwargs since it is the only way to pass our data to
271 # we hook into kwargs since it is the only way to pass our data to
272 # the celery worker
272 # the celery worker
273 environ = maybe_prepare_env(req)
273 environ = maybe_prepare_env(req)
274 options['headers'] = options.get('headers', {})
274 options['headers'] = options.get('headers', {})
275 options['headers'].update({
275 options['headers'].update({
276 'rhodecode_proxy_data': {
276 'rhodecode_proxy_data': {
277 'environ': environ,
277 'environ': environ,
278 'auth_user': {
278 'auth_user': {
279 'ip_addr': ip_addr,
279 'ip_addr': ip_addr,
280 'user_id': user_id
280 'user_id': user_id
281 },
281 },
282 }
282 }
283 })
283 })
284
284
285 return super(RequestContextTask, self).apply_async(
285 return super(RequestContextTask, self).apply_async(
286 args, kwargs, task_id, producer, link, link_error, shadow, **options)
286 args, kwargs, task_id, producer, link, link_error, shadow, **options)
287
287
288 def __call__(self, *args, **kwargs):
288 def __call__(self, *args, **kwargs):
289 """ rebuild the context and then run task on celery worker """
289 """ rebuild the context and then run task on celery worker """
290
290
291 proxy_data = getattr(self.request, 'rhodecode_proxy_data', None)
291 proxy_data = getattr(self.request, 'rhodecode_proxy_data', None)
292 if not proxy_data:
292 if not proxy_data:
293 return super(RequestContextTask, self).__call__(*args, **kwargs)
293 return super(RequestContextTask, self).__call__(*args, **kwargs)
294
294
295 log.debug('using celery proxy data to run task: %r', proxy_data)
295 log.debug('using celery proxy data to run task: %r', proxy_data)
296 # re-inject and register threadlocals for proper routing support
296 # re-inject and register threadlocals for proper routing support
297 request = prepare_request(proxy_data['environ'])
297 request = prepare_request(proxy_data['environ'])
298 request.user = AuthUser(user_id=proxy_data['auth_user']['user_id'],
298 request.user = AuthUser(user_id=proxy_data['auth_user']['user_id'],
299 ip_addr=proxy_data['auth_user']['ip_addr'])
299 ip_addr=proxy_data['auth_user']['ip_addr'])
300
300
301 return super(RequestContextTask, self).__call__(*args, **kwargs)
301 return super(RequestContextTask, self).__call__(*args, **kwargs)
302
302
@@ -1,4334 +1,4334 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, 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, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @property
679 @property
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self):
683 def get_feed_token(self):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
687 .all()
687 .all()
688 if feed_tokens:
688 if feed_tokens:
689 return feed_tokens[0].api_key
689 return feed_tokens[0].api_key
690 return 'NO_FEED_TOKEN_AVAILABLE'
690 return 'NO_FEED_TOKEN_AVAILABLE'
691
691
692 @classmethod
692 @classmethod
693 def get(cls, user_id, cache=False):
693 def get(cls, user_id, cache=False):
694 if not user_id:
694 if not user_id:
695 return
695 return
696
696
697 user = cls.query()
697 user = cls.query()
698 if cache:
698 if cache:
699 user = user.options(
699 user = user.options(
700 FromCache("sql_cache_short", "get_users_%s" % user_id))
700 FromCache("sql_cache_short", "get_users_%s" % user_id))
701 return user.get(user_id)
701 return user.get(user_id)
702
702
703 @classmethod
703 @classmethod
704 def extra_valid_auth_tokens(cls, user, role=None):
704 def extra_valid_auth_tokens(cls, user, role=None):
705 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
705 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
706 .filter(or_(UserApiKeys.expires == -1,
706 .filter(or_(UserApiKeys.expires == -1,
707 UserApiKeys.expires >= time.time()))
707 UserApiKeys.expires >= time.time()))
708 if role:
708 if role:
709 tokens = tokens.filter(or_(UserApiKeys.role == role,
709 tokens = tokens.filter(or_(UserApiKeys.role == role,
710 UserApiKeys.role == UserApiKeys.ROLE_ALL))
710 UserApiKeys.role == UserApiKeys.ROLE_ALL))
711 return tokens.all()
711 return tokens.all()
712
712
713 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
713 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
714 from rhodecode.lib import auth
714 from rhodecode.lib import auth
715
715
716 log.debug('Trying to authenticate user: %s via auth-token, '
716 log.debug('Trying to authenticate user: %s via auth-token, '
717 'and roles: %s', self, roles)
717 'and roles: %s', self, roles)
718
718
719 if not auth_token:
719 if not auth_token:
720 return False
720 return False
721
721
722 crypto_backend = auth.crypto_backend()
722 crypto_backend = auth.crypto_backend()
723
723
724 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
724 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
725 tokens_q = UserApiKeys.query()\
725 tokens_q = UserApiKeys.query()\
726 .filter(UserApiKeys.user_id == self.user_id)\
726 .filter(UserApiKeys.user_id == self.user_id)\
727 .filter(or_(UserApiKeys.expires == -1,
727 .filter(or_(UserApiKeys.expires == -1,
728 UserApiKeys.expires >= time.time()))
728 UserApiKeys.expires >= time.time()))
729
729
730 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
730 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
731
731
732 plain_tokens = []
732 plain_tokens = []
733 hash_tokens = []
733 hash_tokens = []
734
734
735 for token in tokens_q.all():
735 for token in tokens_q.all():
736 # verify scope first
736 # verify scope first
737 if token.repo_id:
737 if token.repo_id:
738 # token has a scope, we need to verify it
738 # token has a scope, we need to verify it
739 if scope_repo_id != token.repo_id:
739 if scope_repo_id != token.repo_id:
740 log.debug(
740 log.debug(
741 'Scope mismatch: token has a set repo scope: %s, '
741 'Scope mismatch: token has a set repo scope: %s, '
742 'and calling scope is:%s, skipping further checks',
742 'and calling scope is:%s, skipping further checks',
743 token.repo, scope_repo_id)
743 token.repo, scope_repo_id)
744 # token has a scope, and it doesn't match, skip token
744 # token has a scope, and it doesn't match, skip token
745 continue
745 continue
746
746
747 if token.api_key.startswith(crypto_backend.ENC_PREF):
747 if token.api_key.startswith(crypto_backend.ENC_PREF):
748 hash_tokens.append(token.api_key)
748 hash_tokens.append(token.api_key)
749 else:
749 else:
750 plain_tokens.append(token.api_key)
750 plain_tokens.append(token.api_key)
751
751
752 is_plain_match = auth_token in plain_tokens
752 is_plain_match = auth_token in plain_tokens
753 if is_plain_match:
753 if is_plain_match:
754 return True
754 return True
755
755
756 for hashed in hash_tokens:
756 for hashed in hash_tokens:
757 # TODO(marcink): this is expensive to calculate, but most secure
757 # TODO(marcink): this is expensive to calculate, but most secure
758 match = crypto_backend.hash_check(auth_token, hashed)
758 match = crypto_backend.hash_check(auth_token, hashed)
759 if match:
759 if match:
760 return True
760 return True
761
761
762 return False
762 return False
763
763
764 @property
764 @property
765 def ip_addresses(self):
765 def ip_addresses(self):
766 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
766 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
767 return [x.ip_addr for x in ret]
767 return [x.ip_addr for x in ret]
768
768
769 @property
769 @property
770 def username_and_name(self):
770 def username_and_name(self):
771 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
771 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
772
772
773 @property
773 @property
774 def username_or_name_or_email(self):
774 def username_or_name_or_email(self):
775 full_name = self.full_name if self.full_name is not ' ' else None
775 full_name = self.full_name if self.full_name is not ' ' else None
776 return self.username or full_name or self.email
776 return self.username or full_name or self.email
777
777
778 @property
778 @property
779 def full_name(self):
779 def full_name(self):
780 return '%s %s' % (self.first_name, self.last_name)
780 return '%s %s' % (self.first_name, self.last_name)
781
781
782 @property
782 @property
783 def full_name_or_username(self):
783 def full_name_or_username(self):
784 return ('%s %s' % (self.first_name, self.last_name)
784 return ('%s %s' % (self.first_name, self.last_name)
785 if (self.first_name and self.last_name) else self.username)
785 if (self.first_name and self.last_name) else self.username)
786
786
787 @property
787 @property
788 def full_contact(self):
788 def full_contact(self):
789 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
789 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
790
790
791 @property
791 @property
792 def short_contact(self):
792 def short_contact(self):
793 return '%s %s' % (self.first_name, self.last_name)
793 return '%s %s' % (self.first_name, self.last_name)
794
794
795 @property
795 @property
796 def is_admin(self):
796 def is_admin(self):
797 return self.admin
797 return self.admin
798
798
799 def AuthUser(self, **kwargs):
799 def AuthUser(self, **kwargs):
800 """
800 """
801 Returns instance of AuthUser for this user
801 Returns instance of AuthUser for this user
802 """
802 """
803 from rhodecode.lib.auth import AuthUser
803 from rhodecode.lib.auth import AuthUser
804 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
804 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
805
805
806 @hybrid_property
806 @hybrid_property
807 def user_data(self):
807 def user_data(self):
808 if not self._user_data:
808 if not self._user_data:
809 return {}
809 return {}
810
810
811 try:
811 try:
812 return json.loads(self._user_data)
812 return json.loads(self._user_data)
813 except TypeError:
813 except TypeError:
814 return {}
814 return {}
815
815
816 @user_data.setter
816 @user_data.setter
817 def user_data(self, val):
817 def user_data(self, val):
818 if not isinstance(val, dict):
818 if not isinstance(val, dict):
819 raise Exception('user_data must be dict, got %s' % type(val))
819 raise Exception('user_data must be dict, got %s' % type(val))
820 try:
820 try:
821 self._user_data = json.dumps(val)
821 self._user_data = json.dumps(val)
822 except Exception:
822 except Exception:
823 log.error(traceback.format_exc())
823 log.error(traceback.format_exc())
824
824
825 @classmethod
825 @classmethod
826 def get_by_username(cls, username, case_insensitive=False,
826 def get_by_username(cls, username, case_insensitive=False,
827 cache=False, identity_cache=False):
827 cache=False, identity_cache=False):
828 session = Session()
828 session = Session()
829
829
830 if case_insensitive:
830 if case_insensitive:
831 q = cls.query().filter(
831 q = cls.query().filter(
832 func.lower(cls.username) == func.lower(username))
832 func.lower(cls.username) == func.lower(username))
833 else:
833 else:
834 q = cls.query().filter(cls.username == username)
834 q = cls.query().filter(cls.username == username)
835
835
836 if cache:
836 if cache:
837 if identity_cache:
837 if identity_cache:
838 val = cls.identity_cache(session, 'username', username)
838 val = cls.identity_cache(session, 'username', username)
839 if val:
839 if val:
840 return val
840 return val
841 else:
841 else:
842 cache_key = "get_user_by_name_%s" % _hash_key(username)
842 cache_key = "get_user_by_name_%s" % _hash_key(username)
843 q = q.options(
843 q = q.options(
844 FromCache("sql_cache_short", cache_key))
844 FromCache("sql_cache_short", cache_key))
845
845
846 return q.scalar()
846 return q.scalar()
847
847
848 @classmethod
848 @classmethod
849 def get_by_auth_token(cls, auth_token, cache=False):
849 def get_by_auth_token(cls, auth_token, cache=False):
850 q = UserApiKeys.query()\
850 q = UserApiKeys.query()\
851 .filter(UserApiKeys.api_key == auth_token)\
851 .filter(UserApiKeys.api_key == auth_token)\
852 .filter(or_(UserApiKeys.expires == -1,
852 .filter(or_(UserApiKeys.expires == -1,
853 UserApiKeys.expires >= time.time()))
853 UserApiKeys.expires >= time.time()))
854 if cache:
854 if cache:
855 q = q.options(
855 q = q.options(
856 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
856 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
857
857
858 match = q.first()
858 match = q.first()
859 if match:
859 if match:
860 return match.user
860 return match.user
861
861
862 @classmethod
862 @classmethod
863 def get_by_email(cls, email, case_insensitive=False, cache=False):
863 def get_by_email(cls, email, case_insensitive=False, cache=False):
864
864
865 if case_insensitive:
865 if case_insensitive:
866 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
866 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
867
867
868 else:
868 else:
869 q = cls.query().filter(cls.email == email)
869 q = cls.query().filter(cls.email == email)
870
870
871 email_key = _hash_key(email)
871 email_key = _hash_key(email)
872 if cache:
872 if cache:
873 q = q.options(
873 q = q.options(
874 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
874 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
875
875
876 ret = q.scalar()
876 ret = q.scalar()
877 if ret is None:
877 if ret is None:
878 q = UserEmailMap.query()
878 q = UserEmailMap.query()
879 # try fetching in alternate email map
879 # try fetching in alternate email map
880 if case_insensitive:
880 if case_insensitive:
881 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
881 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
882 else:
882 else:
883 q = q.filter(UserEmailMap.email == email)
883 q = q.filter(UserEmailMap.email == email)
884 q = q.options(joinedload(UserEmailMap.user))
884 q = q.options(joinedload(UserEmailMap.user))
885 if cache:
885 if cache:
886 q = q.options(
886 q = q.options(
887 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
887 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
888 ret = getattr(q.scalar(), 'user', None)
888 ret = getattr(q.scalar(), 'user', None)
889
889
890 return ret
890 return ret
891
891
892 @classmethod
892 @classmethod
893 def get_from_cs_author(cls, author):
893 def get_from_cs_author(cls, author):
894 """
894 """
895 Tries to get User objects out of commit author string
895 Tries to get User objects out of commit author string
896
896
897 :param author:
897 :param author:
898 """
898 """
899 from rhodecode.lib.helpers import email, author_name
899 from rhodecode.lib.helpers import email, author_name
900 # Valid email in the attribute passed, see if they're in the system
900 # Valid email in the attribute passed, see if they're in the system
901 _email = email(author)
901 _email = email(author)
902 if _email:
902 if _email:
903 user = cls.get_by_email(_email, case_insensitive=True)
903 user = cls.get_by_email(_email, case_insensitive=True)
904 if user:
904 if user:
905 return user
905 return user
906 # Maybe we can match by username?
906 # Maybe we can match by username?
907 _author = author_name(author)
907 _author = author_name(author)
908 user = cls.get_by_username(_author, case_insensitive=True)
908 user = cls.get_by_username(_author, case_insensitive=True)
909 if user:
909 if user:
910 return user
910 return user
911
911
912 def update_userdata(self, **kwargs):
912 def update_userdata(self, **kwargs):
913 usr = self
913 usr = self
914 old = usr.user_data
914 old = usr.user_data
915 old.update(**kwargs)
915 old.update(**kwargs)
916 usr.user_data = old
916 usr.user_data = old
917 Session().add(usr)
917 Session().add(usr)
918 log.debug('updated userdata with ', kwargs)
918 log.debug('updated userdata with ', kwargs)
919
919
920 def update_lastlogin(self):
920 def update_lastlogin(self):
921 """Update user lastlogin"""
921 """Update user lastlogin"""
922 self.last_login = datetime.datetime.now()
922 self.last_login = datetime.datetime.now()
923 Session().add(self)
923 Session().add(self)
924 log.debug('updated user %s lastlogin', self.username)
924 log.debug('updated user %s lastlogin', self.username)
925
925
926 def update_lastactivity(self):
926 def update_lastactivity(self):
927 """Update user lastactivity"""
927 """Update user lastactivity"""
928 self.last_activity = datetime.datetime.now()
928 self.last_activity = datetime.datetime.now()
929 Session().add(self)
929 Session().add(self)
930 log.debug('updated user `%s` last activity', self.username)
930 log.debug('updated user `%s` last activity', self.username)
931
931
932 def update_password(self, new_password):
932 def update_password(self, new_password):
933 from rhodecode.lib.auth import get_crypt_password
933 from rhodecode.lib.auth import get_crypt_password
934
934
935 self.password = get_crypt_password(new_password)
935 self.password = get_crypt_password(new_password)
936 Session().add(self)
936 Session().add(self)
937
937
938 @classmethod
938 @classmethod
939 def get_first_super_admin(cls):
939 def get_first_super_admin(cls):
940 user = User.query().filter(User.admin == true()).first()
940 user = User.query().filter(User.admin == true()).first()
941 if user is None:
941 if user is None:
942 raise Exception('FATAL: Missing administrative account!')
942 raise Exception('FATAL: Missing administrative account!')
943 return user
943 return user
944
944
945 @classmethod
945 @classmethod
946 def get_all_super_admins(cls):
946 def get_all_super_admins(cls):
947 """
947 """
948 Returns all admin accounts sorted by username
948 Returns all admin accounts sorted by username
949 """
949 """
950 return User.query().filter(User.admin == true())\
950 return User.query().filter(User.admin == true())\
951 .order_by(User.username.asc()).all()
951 .order_by(User.username.asc()).all()
952
952
953 @classmethod
953 @classmethod
954 def get_default_user(cls, cache=False, refresh=False):
954 def get_default_user(cls, cache=False, refresh=False):
955 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
955 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
956 if user is None:
956 if user is None:
957 raise Exception('FATAL: Missing default account!')
957 raise Exception('FATAL: Missing default account!')
958 if refresh:
958 if refresh:
959 # The default user might be based on outdated state which
959 # The default user might be based on outdated state which
960 # has been loaded from the cache.
960 # has been loaded from the cache.
961 # A call to refresh() ensures that the
961 # A call to refresh() ensures that the
962 # latest state from the database is used.
962 # latest state from the database is used.
963 Session().refresh(user)
963 Session().refresh(user)
964 return user
964 return user
965
965
966 def _get_default_perms(self, user, suffix=''):
966 def _get_default_perms(self, user, suffix=''):
967 from rhodecode.model.permission import PermissionModel
967 from rhodecode.model.permission import PermissionModel
968 return PermissionModel().get_default_perms(user.user_perms, suffix)
968 return PermissionModel().get_default_perms(user.user_perms, suffix)
969
969
970 def get_default_perms(self, suffix=''):
970 def get_default_perms(self, suffix=''):
971 return self._get_default_perms(self, suffix)
971 return self._get_default_perms(self, suffix)
972
972
973 def get_api_data(self, include_secrets=False, details='full'):
973 def get_api_data(self, include_secrets=False, details='full'):
974 """
974 """
975 Common function for generating user related data for API
975 Common function for generating user related data for API
976
976
977 :param include_secrets: By default secrets in the API data will be replaced
977 :param include_secrets: By default secrets in the API data will be replaced
978 by a placeholder value to prevent exposing this data by accident. In case
978 by a placeholder value to prevent exposing this data by accident. In case
979 this data shall be exposed, set this flag to ``True``.
979 this data shall be exposed, set this flag to ``True``.
980
980
981 :param details: details can be 'basic|full' basic gives only a subset of
981 :param details: details can be 'basic|full' basic gives only a subset of
982 the available user information that includes user_id, name and emails.
982 the available user information that includes user_id, name and emails.
983 """
983 """
984 user = self
984 user = self
985 user_data = self.user_data
985 user_data = self.user_data
986 data = {
986 data = {
987 'user_id': user.user_id,
987 'user_id': user.user_id,
988 'username': user.username,
988 'username': user.username,
989 'firstname': user.name,
989 'firstname': user.name,
990 'lastname': user.lastname,
990 'lastname': user.lastname,
991 'email': user.email,
991 'email': user.email,
992 'emails': user.emails,
992 'emails': user.emails,
993 }
993 }
994 if details == 'basic':
994 if details == 'basic':
995 return data
995 return data
996
996
997 auth_token_length = 40
997 auth_token_length = 40
998 auth_token_replacement = '*' * auth_token_length
998 auth_token_replacement = '*' * auth_token_length
999
999
1000 extras = {
1000 extras = {
1001 'auth_tokens': [auth_token_replacement],
1001 'auth_tokens': [auth_token_replacement],
1002 'active': user.active,
1002 'active': user.active,
1003 'admin': user.admin,
1003 'admin': user.admin,
1004 'extern_type': user.extern_type,
1004 'extern_type': user.extern_type,
1005 'extern_name': user.extern_name,
1005 'extern_name': user.extern_name,
1006 'last_login': user.last_login,
1006 'last_login': user.last_login,
1007 'last_activity': user.last_activity,
1007 'last_activity': user.last_activity,
1008 'ip_addresses': user.ip_addresses,
1008 'ip_addresses': user.ip_addresses,
1009 'language': user_data.get('language')
1009 'language': user_data.get('language')
1010 }
1010 }
1011 data.update(extras)
1011 data.update(extras)
1012
1012
1013 if include_secrets:
1013 if include_secrets:
1014 data['auth_tokens'] = user.auth_tokens
1014 data['auth_tokens'] = user.auth_tokens
1015 return data
1015 return data
1016
1016
1017 def __json__(self):
1017 def __json__(self):
1018 data = {
1018 data = {
1019 'full_name': self.full_name,
1019 'full_name': self.full_name,
1020 'full_name_or_username': self.full_name_or_username,
1020 'full_name_or_username': self.full_name_or_username,
1021 'short_contact': self.short_contact,
1021 'short_contact': self.short_contact,
1022 'full_contact': self.full_contact,
1022 'full_contact': self.full_contact,
1023 }
1023 }
1024 data.update(self.get_api_data())
1024 data.update(self.get_api_data())
1025 return data
1025 return data
1026
1026
1027
1027
1028 class UserApiKeys(Base, BaseModel):
1028 class UserApiKeys(Base, BaseModel):
1029 __tablename__ = 'user_api_keys'
1029 __tablename__ = 'user_api_keys'
1030 __table_args__ = (
1030 __table_args__ = (
1031 Index('uak_api_key_idx', 'api_key', unique=True),
1031 Index('uak_api_key_idx', 'api_key', unique=True),
1032 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1032 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1035 )
1035 )
1036 __mapper_args__ = {}
1036 __mapper_args__ = {}
1037
1037
1038 # ApiKey role
1038 # ApiKey role
1039 ROLE_ALL = 'token_role_all'
1039 ROLE_ALL = 'token_role_all'
1040 ROLE_HTTP = 'token_role_http'
1040 ROLE_HTTP = 'token_role_http'
1041 ROLE_VCS = 'token_role_vcs'
1041 ROLE_VCS = 'token_role_vcs'
1042 ROLE_API = 'token_role_api'
1042 ROLE_API = 'token_role_api'
1043 ROLE_FEED = 'token_role_feed'
1043 ROLE_FEED = 'token_role_feed'
1044 ROLE_PASSWORD_RESET = 'token_password_reset'
1044 ROLE_PASSWORD_RESET = 'token_password_reset'
1045
1045
1046 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1046 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1047
1047
1048 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1048 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1050 api_key = Column("api_key", String(255), nullable=False, unique=True)
1050 api_key = Column("api_key", String(255), nullable=False, unique=True)
1051 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1051 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1052 expires = Column('expires', Float(53), nullable=False)
1052 expires = Column('expires', Float(53), nullable=False)
1053 role = Column('role', String(255), nullable=True)
1053 role = Column('role', String(255), nullable=True)
1054 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1054 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1055
1055
1056 # scope columns
1056 # scope columns
1057 repo_id = Column(
1057 repo_id = Column(
1058 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1058 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1059 nullable=True, unique=None, default=None)
1059 nullable=True, unique=None, default=None)
1060 repo = relationship('Repository', lazy='joined')
1060 repo = relationship('Repository', lazy='joined')
1061
1061
1062 repo_group_id = Column(
1062 repo_group_id = Column(
1063 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1063 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1064 nullable=True, unique=None, default=None)
1064 nullable=True, unique=None, default=None)
1065 repo_group = relationship('RepoGroup', lazy='joined')
1065 repo_group = relationship('RepoGroup', lazy='joined')
1066
1066
1067 user = relationship('User', lazy='joined')
1067 user = relationship('User', lazy='joined')
1068
1068
1069 def __unicode__(self):
1069 def __unicode__(self):
1070 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1070 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1071
1071
1072 def __json__(self):
1072 def __json__(self):
1073 data = {
1073 data = {
1074 'auth_token': self.api_key,
1074 'auth_token': self.api_key,
1075 'role': self.role,
1075 'role': self.role,
1076 'scope': self.scope_humanized,
1076 'scope': self.scope_humanized,
1077 'expired': self.expired
1077 'expired': self.expired
1078 }
1078 }
1079 return data
1079 return data
1080
1080
1081 def get_api_data(self, include_secrets=False):
1081 def get_api_data(self, include_secrets=False):
1082 data = self.__json__()
1082 data = self.__json__()
1083 if include_secrets:
1083 if include_secrets:
1084 return data
1084 return data
1085 else:
1085 else:
1086 data['auth_token'] = self.token_obfuscated
1086 data['auth_token'] = self.token_obfuscated
1087 return data
1087 return data
1088
1088
1089 @hybrid_property
1089 @hybrid_property
1090 def description_safe(self):
1090 def description_safe(self):
1091 from rhodecode.lib import helpers as h
1091 from rhodecode.lib import helpers as h
1092 return h.escape(self.description)
1092 return h.escape(self.description)
1093
1093
1094 @property
1094 @property
1095 def expired(self):
1095 def expired(self):
1096 if self.expires == -1:
1096 if self.expires == -1:
1097 return False
1097 return False
1098 return time.time() > self.expires
1098 return time.time() > self.expires
1099
1099
1100 @classmethod
1100 @classmethod
1101 def _get_role_name(cls, role):
1101 def _get_role_name(cls, role):
1102 return {
1102 return {
1103 cls.ROLE_ALL: _('all'),
1103 cls.ROLE_ALL: _('all'),
1104 cls.ROLE_HTTP: _('http/web interface'),
1104 cls.ROLE_HTTP: _('http/web interface'),
1105 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1105 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1106 cls.ROLE_API: _('api calls'),
1106 cls.ROLE_API: _('api calls'),
1107 cls.ROLE_FEED: _('feed access'),
1107 cls.ROLE_FEED: _('feed access'),
1108 }.get(role, role)
1108 }.get(role, role)
1109
1109
1110 @property
1110 @property
1111 def role_humanized(self):
1111 def role_humanized(self):
1112 return self._get_role_name(self.role)
1112 return self._get_role_name(self.role)
1113
1113
1114 def _get_scope(self):
1114 def _get_scope(self):
1115 if self.repo:
1115 if self.repo:
1116 return repr(self.repo)
1116 return repr(self.repo)
1117 if self.repo_group:
1117 if self.repo_group:
1118 return repr(self.repo_group) + ' (recursive)'
1118 return repr(self.repo_group) + ' (recursive)'
1119 return 'global'
1119 return 'global'
1120
1120
1121 @property
1121 @property
1122 def scope_humanized(self):
1122 def scope_humanized(self):
1123 return self._get_scope()
1123 return self._get_scope()
1124
1124
1125 @property
1125 @property
1126 def token_obfuscated(self):
1126 def token_obfuscated(self):
1127 if self.api_key:
1127 if self.api_key:
1128 return self.api_key[:4] + "****"
1128 return self.api_key[:4] + "****"
1129
1129
1130
1130
1131 class UserEmailMap(Base, BaseModel):
1131 class UserEmailMap(Base, BaseModel):
1132 __tablename__ = 'user_email_map'
1132 __tablename__ = 'user_email_map'
1133 __table_args__ = (
1133 __table_args__ = (
1134 Index('uem_email_idx', 'email'),
1134 Index('uem_email_idx', 'email'),
1135 UniqueConstraint('email'),
1135 UniqueConstraint('email'),
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1137 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1137 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1138 )
1138 )
1139 __mapper_args__ = {}
1139 __mapper_args__ = {}
1140
1140
1141 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1141 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1142 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1142 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1143 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1143 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1144 user = relationship('User', lazy='joined')
1144 user = relationship('User', lazy='joined')
1145
1145
1146 @validates('_email')
1146 @validates('_email')
1147 def validate_email(self, key, email):
1147 def validate_email(self, key, email):
1148 # check if this email is not main one
1148 # check if this email is not main one
1149 main_email = Session().query(User).filter(User.email == email).scalar()
1149 main_email = Session().query(User).filter(User.email == email).scalar()
1150 if main_email is not None:
1150 if main_email is not None:
1151 raise AttributeError('email %s is present is user table' % email)
1151 raise AttributeError('email %s is present is user table' % email)
1152 return email
1152 return email
1153
1153
1154 @hybrid_property
1154 @hybrid_property
1155 def email(self):
1155 def email(self):
1156 return self._email
1156 return self._email
1157
1157
1158 @email.setter
1158 @email.setter
1159 def email(self, val):
1159 def email(self, val):
1160 self._email = val.lower() if val else None
1160 self._email = val.lower() if val else None
1161
1161
1162
1162
1163 class UserIpMap(Base, BaseModel):
1163 class UserIpMap(Base, BaseModel):
1164 __tablename__ = 'user_ip_map'
1164 __tablename__ = 'user_ip_map'
1165 __table_args__ = (
1165 __table_args__ = (
1166 UniqueConstraint('user_id', 'ip_addr'),
1166 UniqueConstraint('user_id', 'ip_addr'),
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1169 )
1169 )
1170 __mapper_args__ = {}
1170 __mapper_args__ = {}
1171
1171
1172 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1174 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1174 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1175 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1175 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1176 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1176 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1177 user = relationship('User', lazy='joined')
1177 user = relationship('User', lazy='joined')
1178
1178
1179 @hybrid_property
1179 @hybrid_property
1180 def description_safe(self):
1180 def description_safe(self):
1181 from rhodecode.lib import helpers as h
1181 from rhodecode.lib import helpers as h
1182 return h.escape(self.description)
1182 return h.escape(self.description)
1183
1183
1184 @classmethod
1184 @classmethod
1185 def _get_ip_range(cls, ip_addr):
1185 def _get_ip_range(cls, ip_addr):
1186 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1186 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1187 return [str(net.network_address), str(net.broadcast_address)]
1187 return [str(net.network_address), str(net.broadcast_address)]
1188
1188
1189 def __json__(self):
1189 def __json__(self):
1190 return {
1190 return {
1191 'ip_addr': self.ip_addr,
1191 'ip_addr': self.ip_addr,
1192 'ip_range': self._get_ip_range(self.ip_addr),
1192 'ip_range': self._get_ip_range(self.ip_addr),
1193 }
1193 }
1194
1194
1195 def __unicode__(self):
1195 def __unicode__(self):
1196 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1196 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1197 self.user_id, self.ip_addr)
1197 self.user_id, self.ip_addr)
1198
1198
1199
1199
1200 class UserSshKeys(Base, BaseModel):
1200 class UserSshKeys(Base, BaseModel):
1201 __tablename__ = 'user_ssh_keys'
1201 __tablename__ = 'user_ssh_keys'
1202 __table_args__ = (
1202 __table_args__ = (
1203 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1203 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1204
1204
1205 UniqueConstraint('ssh_key_fingerprint'),
1205 UniqueConstraint('ssh_key_fingerprint'),
1206
1206
1207 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1207 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1208 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1208 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1209 )
1209 )
1210 __mapper_args__ = {}
1210 __mapper_args__ = {}
1211
1211
1212 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1212 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1213 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1213 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1214 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1214 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1215
1215
1216 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1216 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1217
1217
1218 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1218 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1219 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1219 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1220 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1220 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1221
1221
1222 user = relationship('User', lazy='joined')
1222 user = relationship('User', lazy='joined')
1223
1223
1224 def __json__(self):
1224 def __json__(self):
1225 data = {
1225 data = {
1226 'ssh_fingerprint': self.ssh_key_fingerprint,
1226 'ssh_fingerprint': self.ssh_key_fingerprint,
1227 'description': self.description,
1227 'description': self.description,
1228 'created_on': self.created_on
1228 'created_on': self.created_on
1229 }
1229 }
1230 return data
1230 return data
1231
1231
1232 def get_api_data(self):
1232 def get_api_data(self):
1233 data = self.__json__()
1233 data = self.__json__()
1234 return data
1234 return data
1235
1235
1236
1236
1237 class UserLog(Base, BaseModel):
1237 class UserLog(Base, BaseModel):
1238 __tablename__ = 'user_logs'
1238 __tablename__ = 'user_logs'
1239 __table_args__ = (
1239 __table_args__ = (
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1241 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1241 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1242 )
1242 )
1243 VERSION_1 = 'v1'
1243 VERSION_1 = 'v1'
1244 VERSION_2 = 'v2'
1244 VERSION_2 = 'v2'
1245 VERSIONS = [VERSION_1, VERSION_2]
1245 VERSIONS = [VERSION_1, VERSION_2]
1246
1246
1247 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1249 username = Column("username", String(255), nullable=True, unique=None, default=None)
1249 username = Column("username", String(255), nullable=True, unique=None, default=None)
1250 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1250 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1251 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1251 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1252 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1252 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1253 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1253 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1254 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1254 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1255
1255
1256 version = Column("version", String(255), nullable=True, default=VERSION_1)
1256 version = Column("version", String(255), nullable=True, default=VERSION_1)
1257 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1257 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1258 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1258 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1259
1259
1260 def __unicode__(self):
1260 def __unicode__(self):
1261 return u"<%s('id:%s:%s')>" % (
1261 return u"<%s('id:%s:%s')>" % (
1262 self.__class__.__name__, self.repository_name, self.action)
1262 self.__class__.__name__, self.repository_name, self.action)
1263
1263
1264 def __json__(self):
1264 def __json__(self):
1265 return {
1265 return {
1266 'user_id': self.user_id,
1266 'user_id': self.user_id,
1267 'username': self.username,
1267 'username': self.username,
1268 'repository_id': self.repository_id,
1268 'repository_id': self.repository_id,
1269 'repository_name': self.repository_name,
1269 'repository_name': self.repository_name,
1270 'user_ip': self.user_ip,
1270 'user_ip': self.user_ip,
1271 'action_date': self.action_date,
1271 'action_date': self.action_date,
1272 'action': self.action,
1272 'action': self.action,
1273 }
1273 }
1274
1274
1275 @hybrid_property
1275 @hybrid_property
1276 def entry_id(self):
1276 def entry_id(self):
1277 return self.user_log_id
1277 return self.user_log_id
1278
1278
1279 @property
1279 @property
1280 def action_as_day(self):
1280 def action_as_day(self):
1281 return datetime.date(*self.action_date.timetuple()[:3])
1281 return datetime.date(*self.action_date.timetuple()[:3])
1282
1282
1283 user = relationship('User')
1283 user = relationship('User')
1284 repository = relationship('Repository', cascade='')
1284 repository = relationship('Repository', cascade='')
1285
1285
1286
1286
1287 class UserGroup(Base, BaseModel):
1287 class UserGroup(Base, BaseModel):
1288 __tablename__ = 'users_groups'
1288 __tablename__ = 'users_groups'
1289 __table_args__ = (
1289 __table_args__ = (
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1292 )
1292 )
1293
1293
1294 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1294 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1295 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1295 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1296 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1296 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1297 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1297 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1298 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1298 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1300 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1300 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1301 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1301 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1302
1302
1303 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1303 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1304 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1304 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1305 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1305 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1306 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1306 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1307 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1307 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1308 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1308 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1309
1309
1310 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1310 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1311 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1311 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1312
1312
1313 @classmethod
1313 @classmethod
1314 def _load_group_data(cls, column):
1314 def _load_group_data(cls, column):
1315 if not column:
1315 if not column:
1316 return {}
1316 return {}
1317
1317
1318 try:
1318 try:
1319 return json.loads(column) or {}
1319 return json.loads(column) or {}
1320 except TypeError:
1320 except TypeError:
1321 return {}
1321 return {}
1322
1322
1323 @hybrid_property
1323 @hybrid_property
1324 def description_safe(self):
1324 def description_safe(self):
1325 from rhodecode.lib import helpers as h
1325 from rhodecode.lib import helpers as h
1326 return h.escape(self.description)
1326 return h.escape(self.description)
1327
1327
1328 @hybrid_property
1328 @hybrid_property
1329 def group_data(self):
1329 def group_data(self):
1330 return self._load_group_data(self._group_data)
1330 return self._load_group_data(self._group_data)
1331
1331
1332 @group_data.expression
1332 @group_data.expression
1333 def group_data(self, **kwargs):
1333 def group_data(self, **kwargs):
1334 return self._group_data
1334 return self._group_data
1335
1335
1336 @group_data.setter
1336 @group_data.setter
1337 def group_data(self, val):
1337 def group_data(self, val):
1338 try:
1338 try:
1339 self._group_data = json.dumps(val)
1339 self._group_data = json.dumps(val)
1340 except Exception:
1340 except Exception:
1341 log.error(traceback.format_exc())
1341 log.error(traceback.format_exc())
1342
1342
1343 def __unicode__(self):
1343 def __unicode__(self):
1344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1345 self.users_group_id,
1345 self.users_group_id,
1346 self.users_group_name)
1346 self.users_group_name)
1347
1347
1348 @classmethod
1348 @classmethod
1349 def get_by_group_name(cls, group_name, cache=False,
1349 def get_by_group_name(cls, group_name, cache=False,
1350 case_insensitive=False):
1350 case_insensitive=False):
1351 if case_insensitive:
1351 if case_insensitive:
1352 q = cls.query().filter(func.lower(cls.users_group_name) ==
1352 q = cls.query().filter(func.lower(cls.users_group_name) ==
1353 func.lower(group_name))
1353 func.lower(group_name))
1354
1354
1355 else:
1355 else:
1356 q = cls.query().filter(cls.users_group_name == group_name)
1356 q = cls.query().filter(cls.users_group_name == group_name)
1357 if cache:
1357 if cache:
1358 q = q.options(
1358 q = q.options(
1359 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1359 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1360 return q.scalar()
1360 return q.scalar()
1361
1361
1362 @classmethod
1362 @classmethod
1363 def get(cls, user_group_id, cache=False):
1363 def get(cls, user_group_id, cache=False):
1364 if not user_group_id:
1364 if not user_group_id:
1365 return
1365 return
1366
1366
1367 user_group = cls.query()
1367 user_group = cls.query()
1368 if cache:
1368 if cache:
1369 user_group = user_group.options(
1369 user_group = user_group.options(
1370 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1370 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1371 return user_group.get(user_group_id)
1371 return user_group.get(user_group_id)
1372
1372
1373 def permissions(self, with_admins=True, with_owner=True):
1373 def permissions(self, with_admins=True, with_owner=True):
1374 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1374 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1375 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1375 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1376 joinedload(UserUserGroupToPerm.user),
1376 joinedload(UserUserGroupToPerm.user),
1377 joinedload(UserUserGroupToPerm.permission),)
1377 joinedload(UserUserGroupToPerm.permission),)
1378
1378
1379 # get owners and admins and permissions. We do a trick of re-writing
1379 # get owners and admins and permissions. We do a trick of re-writing
1380 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1380 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1381 # has a global reference and changing one object propagates to all
1381 # has a global reference and changing one object propagates to all
1382 # others. This means if admin is also an owner admin_row that change
1382 # others. This means if admin is also an owner admin_row that change
1383 # would propagate to both objects
1383 # would propagate to both objects
1384 perm_rows = []
1384 perm_rows = []
1385 for _usr in q.all():
1385 for _usr in q.all():
1386 usr = AttributeDict(_usr.user.get_dict())
1386 usr = AttributeDict(_usr.user.get_dict())
1387 usr.permission = _usr.permission.permission_name
1387 usr.permission = _usr.permission.permission_name
1388 perm_rows.append(usr)
1388 perm_rows.append(usr)
1389
1389
1390 # filter the perm rows by 'default' first and then sort them by
1390 # filter the perm rows by 'default' first and then sort them by
1391 # admin,write,read,none permissions sorted again alphabetically in
1391 # admin,write,read,none permissions sorted again alphabetically in
1392 # each group
1392 # each group
1393 perm_rows = sorted(perm_rows, key=display_user_sort)
1393 perm_rows = sorted(perm_rows, key=display_user_sort)
1394
1394
1395 _admin_perm = 'usergroup.admin'
1395 _admin_perm = 'usergroup.admin'
1396 owner_row = []
1396 owner_row = []
1397 if with_owner:
1397 if with_owner:
1398 usr = AttributeDict(self.user.get_dict())
1398 usr = AttributeDict(self.user.get_dict())
1399 usr.owner_row = True
1399 usr.owner_row = True
1400 usr.permission = _admin_perm
1400 usr.permission = _admin_perm
1401 owner_row.append(usr)
1401 owner_row.append(usr)
1402
1402
1403 super_admin_rows = []
1403 super_admin_rows = []
1404 if with_admins:
1404 if with_admins:
1405 for usr in User.get_all_super_admins():
1405 for usr in User.get_all_super_admins():
1406 # if this admin is also owner, don't double the record
1406 # if this admin is also owner, don't double the record
1407 if usr.user_id == owner_row[0].user_id:
1407 if usr.user_id == owner_row[0].user_id:
1408 owner_row[0].admin_row = True
1408 owner_row[0].admin_row = True
1409 else:
1409 else:
1410 usr = AttributeDict(usr.get_dict())
1410 usr = AttributeDict(usr.get_dict())
1411 usr.admin_row = True
1411 usr.admin_row = True
1412 usr.permission = _admin_perm
1412 usr.permission = _admin_perm
1413 super_admin_rows.append(usr)
1413 super_admin_rows.append(usr)
1414
1414
1415 return super_admin_rows + owner_row + perm_rows
1415 return super_admin_rows + owner_row + perm_rows
1416
1416
1417 def permission_user_groups(self):
1417 def permission_user_groups(self):
1418 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1418 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1419 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1419 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1420 joinedload(UserGroupUserGroupToPerm.target_user_group),
1420 joinedload(UserGroupUserGroupToPerm.target_user_group),
1421 joinedload(UserGroupUserGroupToPerm.permission),)
1421 joinedload(UserGroupUserGroupToPerm.permission),)
1422
1422
1423 perm_rows = []
1423 perm_rows = []
1424 for _user_group in q.all():
1424 for _user_group in q.all():
1425 usr = AttributeDict(_user_group.user_group.get_dict())
1425 usr = AttributeDict(_user_group.user_group.get_dict())
1426 usr.permission = _user_group.permission.permission_name
1426 usr.permission = _user_group.permission.permission_name
1427 perm_rows.append(usr)
1427 perm_rows.append(usr)
1428
1428
1429 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1429 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1430 return perm_rows
1430 return perm_rows
1431
1431
1432 def _get_default_perms(self, user_group, suffix=''):
1432 def _get_default_perms(self, user_group, suffix=''):
1433 from rhodecode.model.permission import PermissionModel
1433 from rhodecode.model.permission import PermissionModel
1434 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1434 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1435
1435
1436 def get_default_perms(self, suffix=''):
1436 def get_default_perms(self, suffix=''):
1437 return self._get_default_perms(self, suffix)
1437 return self._get_default_perms(self, suffix)
1438
1438
1439 def get_api_data(self, with_group_members=True, include_secrets=False):
1439 def get_api_data(self, with_group_members=True, include_secrets=False):
1440 """
1440 """
1441 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1441 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1442 basically forwarded.
1442 basically forwarded.
1443
1443
1444 """
1444 """
1445 user_group = self
1445 user_group = self
1446 data = {
1446 data = {
1447 'users_group_id': user_group.users_group_id,
1447 'users_group_id': user_group.users_group_id,
1448 'group_name': user_group.users_group_name,
1448 'group_name': user_group.users_group_name,
1449 'group_description': user_group.user_group_description,
1449 'group_description': user_group.user_group_description,
1450 'active': user_group.users_group_active,
1450 'active': user_group.users_group_active,
1451 'owner': user_group.user.username,
1451 'owner': user_group.user.username,
1452 'owner_email': user_group.user.email,
1452 'owner_email': user_group.user.email,
1453 }
1453 }
1454
1454
1455 if with_group_members:
1455 if with_group_members:
1456 users = []
1456 users = []
1457 for user in user_group.members:
1457 for user in user_group.members:
1458 user = user.user
1458 user = user.user
1459 users.append(user.get_api_data(include_secrets=include_secrets))
1459 users.append(user.get_api_data(include_secrets=include_secrets))
1460 data['users'] = users
1460 data['users'] = users
1461
1461
1462 return data
1462 return data
1463
1463
1464
1464
1465 class UserGroupMember(Base, BaseModel):
1465 class UserGroupMember(Base, BaseModel):
1466 __tablename__ = 'users_groups_members'
1466 __tablename__ = 'users_groups_members'
1467 __table_args__ = (
1467 __table_args__ = (
1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1470 )
1470 )
1471
1471
1472 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1472 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1473 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1473 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1475
1475
1476 user = relationship('User', lazy='joined')
1476 user = relationship('User', lazy='joined')
1477 users_group = relationship('UserGroup')
1477 users_group = relationship('UserGroup')
1478
1478
1479 def __init__(self, gr_id='', u_id=''):
1479 def __init__(self, gr_id='', u_id=''):
1480 self.users_group_id = gr_id
1480 self.users_group_id = gr_id
1481 self.user_id = u_id
1481 self.user_id = u_id
1482
1482
1483
1483
1484 class RepositoryField(Base, BaseModel):
1484 class RepositoryField(Base, BaseModel):
1485 __tablename__ = 'repositories_fields'
1485 __tablename__ = 'repositories_fields'
1486 __table_args__ = (
1486 __table_args__ = (
1487 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1487 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1490 )
1490 )
1491 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1491 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1492
1492
1493 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1493 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1494 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1494 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1495 field_key = Column("field_key", String(250))
1495 field_key = Column("field_key", String(250))
1496 field_label = Column("field_label", String(1024), nullable=False)
1496 field_label = Column("field_label", String(1024), nullable=False)
1497 field_value = Column("field_value", String(10000), nullable=False)
1497 field_value = Column("field_value", String(10000), nullable=False)
1498 field_desc = Column("field_desc", String(1024), nullable=False)
1498 field_desc = Column("field_desc", String(1024), nullable=False)
1499 field_type = Column("field_type", String(255), nullable=False, unique=None)
1499 field_type = Column("field_type", String(255), nullable=False, unique=None)
1500 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1500 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1501
1501
1502 repository = relationship('Repository')
1502 repository = relationship('Repository')
1503
1503
1504 @property
1504 @property
1505 def field_key_prefixed(self):
1505 def field_key_prefixed(self):
1506 return 'ex_%s' % self.field_key
1506 return 'ex_%s' % self.field_key
1507
1507
1508 @classmethod
1508 @classmethod
1509 def un_prefix_key(cls, key):
1509 def un_prefix_key(cls, key):
1510 if key.startswith(cls.PREFIX):
1510 if key.startswith(cls.PREFIX):
1511 return key[len(cls.PREFIX):]
1511 return key[len(cls.PREFIX):]
1512 return key
1512 return key
1513
1513
1514 @classmethod
1514 @classmethod
1515 def get_by_key_name(cls, key, repo):
1515 def get_by_key_name(cls, key, repo):
1516 row = cls.query()\
1516 row = cls.query()\
1517 .filter(cls.repository == repo)\
1517 .filter(cls.repository == repo)\
1518 .filter(cls.field_key == key).scalar()
1518 .filter(cls.field_key == key).scalar()
1519 return row
1519 return row
1520
1520
1521
1521
1522 class Repository(Base, BaseModel):
1522 class Repository(Base, BaseModel):
1523 __tablename__ = 'repositories'
1523 __tablename__ = 'repositories'
1524 __table_args__ = (
1524 __table_args__ = (
1525 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1525 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1527 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1527 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1528 )
1528 )
1529 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1529 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1530 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1530 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1531
1531
1532 STATE_CREATED = 'repo_state_created'
1532 STATE_CREATED = 'repo_state_created'
1533 STATE_PENDING = 'repo_state_pending'
1533 STATE_PENDING = 'repo_state_pending'
1534 STATE_ERROR = 'repo_state_error'
1534 STATE_ERROR = 'repo_state_error'
1535
1535
1536 LOCK_AUTOMATIC = 'lock_auto'
1536 LOCK_AUTOMATIC = 'lock_auto'
1537 LOCK_API = 'lock_api'
1537 LOCK_API = 'lock_api'
1538 LOCK_WEB = 'lock_web'
1538 LOCK_WEB = 'lock_web'
1539 LOCK_PULL = 'lock_pull'
1539 LOCK_PULL = 'lock_pull'
1540
1540
1541 NAME_SEP = URL_SEP
1541 NAME_SEP = URL_SEP
1542
1542
1543 repo_id = Column(
1543 repo_id = Column(
1544 "repo_id", Integer(), nullable=False, unique=True, default=None,
1544 "repo_id", Integer(), nullable=False, unique=True, default=None,
1545 primary_key=True)
1545 primary_key=True)
1546 _repo_name = Column(
1546 _repo_name = Column(
1547 "repo_name", Text(), nullable=False, default=None)
1547 "repo_name", Text(), nullable=False, default=None)
1548 _repo_name_hash = Column(
1548 _repo_name_hash = Column(
1549 "repo_name_hash", String(255), nullable=False, unique=True)
1549 "repo_name_hash", String(255), nullable=False, unique=True)
1550 repo_state = Column("repo_state", String(255), nullable=True)
1550 repo_state = Column("repo_state", String(255), nullable=True)
1551
1551
1552 clone_uri = Column(
1552 clone_uri = Column(
1553 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1553 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1554 default=None)
1554 default=None)
1555 repo_type = Column(
1555 repo_type = Column(
1556 "repo_type", String(255), nullable=False, unique=False, default=None)
1556 "repo_type", String(255), nullable=False, unique=False, default=None)
1557 user_id = Column(
1557 user_id = Column(
1558 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1558 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1559 unique=False, default=None)
1559 unique=False, default=None)
1560 private = Column(
1560 private = Column(
1561 "private", Boolean(), nullable=True, unique=None, default=None)
1561 "private", Boolean(), nullable=True, unique=None, default=None)
1562 enable_statistics = Column(
1562 enable_statistics = Column(
1563 "statistics", Boolean(), nullable=True, unique=None, default=True)
1563 "statistics", Boolean(), nullable=True, unique=None, default=True)
1564 enable_downloads = Column(
1564 enable_downloads = Column(
1565 "downloads", Boolean(), nullable=True, unique=None, default=True)
1565 "downloads", Boolean(), nullable=True, unique=None, default=True)
1566 description = Column(
1566 description = Column(
1567 "description", String(10000), nullable=True, unique=None, default=None)
1567 "description", String(10000), nullable=True, unique=None, default=None)
1568 created_on = Column(
1568 created_on = Column(
1569 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1569 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1570 default=datetime.datetime.now)
1570 default=datetime.datetime.now)
1571 updated_on = Column(
1571 updated_on = Column(
1572 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1572 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1573 default=datetime.datetime.now)
1573 default=datetime.datetime.now)
1574 _landing_revision = Column(
1574 _landing_revision = Column(
1575 "landing_revision", String(255), nullable=False, unique=False,
1575 "landing_revision", String(255), nullable=False, unique=False,
1576 default=None)
1576 default=None)
1577 enable_locking = Column(
1577 enable_locking = Column(
1578 "enable_locking", Boolean(), nullable=False, unique=None,
1578 "enable_locking", Boolean(), nullable=False, unique=None,
1579 default=False)
1579 default=False)
1580 _locked = Column(
1580 _locked = Column(
1581 "locked", String(255), nullable=True, unique=False, default=None)
1581 "locked", String(255), nullable=True, unique=False, default=None)
1582 _changeset_cache = Column(
1582 _changeset_cache = Column(
1583 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1583 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1584
1584
1585 fork_id = Column(
1585 fork_id = Column(
1586 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1586 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1587 nullable=True, unique=False, default=None)
1587 nullable=True, unique=False, default=None)
1588 group_id = Column(
1588 group_id = Column(
1589 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1589 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1590 unique=False, default=None)
1590 unique=False, default=None)
1591
1591
1592 user = relationship('User', lazy='joined')
1592 user = relationship('User', lazy='joined')
1593 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1593 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1594 group = relationship('RepoGroup', lazy='joined')
1594 group = relationship('RepoGroup', lazy='joined')
1595 repo_to_perm = relationship(
1595 repo_to_perm = relationship(
1596 'UserRepoToPerm', cascade='all',
1596 'UserRepoToPerm', cascade='all',
1597 order_by='UserRepoToPerm.repo_to_perm_id')
1597 order_by='UserRepoToPerm.repo_to_perm_id')
1598 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1598 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1599 stats = relationship('Statistics', cascade='all', uselist=False)
1599 stats = relationship('Statistics', cascade='all', uselist=False)
1600
1600
1601 followers = relationship(
1601 followers = relationship(
1602 'UserFollowing',
1602 'UserFollowing',
1603 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1603 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1604 cascade='all')
1604 cascade='all')
1605 extra_fields = relationship(
1605 extra_fields = relationship(
1606 'RepositoryField', cascade="all, delete, delete-orphan")
1606 'RepositoryField', cascade="all, delete, delete-orphan")
1607 logs = relationship('UserLog')
1607 logs = relationship('UserLog')
1608 comments = relationship(
1608 comments = relationship(
1609 'ChangesetComment', cascade="all, delete, delete-orphan")
1609 'ChangesetComment', cascade="all, delete, delete-orphan")
1610 pull_requests_source = relationship(
1610 pull_requests_source = relationship(
1611 'PullRequest',
1611 'PullRequest',
1612 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1612 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1613 cascade="all, delete, delete-orphan")
1613 cascade="all, delete, delete-orphan")
1614 pull_requests_target = relationship(
1614 pull_requests_target = relationship(
1615 'PullRequest',
1615 'PullRequest',
1616 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1616 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1617 cascade="all, delete, delete-orphan")
1617 cascade="all, delete, delete-orphan")
1618 ui = relationship('RepoRhodeCodeUi', cascade="all")
1618 ui = relationship('RepoRhodeCodeUi', cascade="all")
1619 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1619 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1620 integrations = relationship('Integration',
1620 integrations = relationship('Integration',
1621 cascade="all, delete, delete-orphan")
1621 cascade="all, delete, delete-orphan")
1622
1622
1623 def __unicode__(self):
1623 def __unicode__(self):
1624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1625 safe_unicode(self.repo_name))
1625 safe_unicode(self.repo_name))
1626
1626
1627 @hybrid_property
1627 @hybrid_property
1628 def description_safe(self):
1628 def description_safe(self):
1629 from rhodecode.lib import helpers as h
1629 from rhodecode.lib import helpers as h
1630 return h.escape(self.description)
1630 return h.escape(self.description)
1631
1631
1632 @hybrid_property
1632 @hybrid_property
1633 def landing_rev(self):
1633 def landing_rev(self):
1634 # always should return [rev_type, rev]
1634 # always should return [rev_type, rev]
1635 if self._landing_revision:
1635 if self._landing_revision:
1636 _rev_info = self._landing_revision.split(':')
1636 _rev_info = self._landing_revision.split(':')
1637 if len(_rev_info) < 2:
1637 if len(_rev_info) < 2:
1638 _rev_info.insert(0, 'rev')
1638 _rev_info.insert(0, 'rev')
1639 return [_rev_info[0], _rev_info[1]]
1639 return [_rev_info[0], _rev_info[1]]
1640 return [None, None]
1640 return [None, None]
1641
1641
1642 @landing_rev.setter
1642 @landing_rev.setter
1643 def landing_rev(self, val):
1643 def landing_rev(self, val):
1644 if ':' not in val:
1644 if ':' not in val:
1645 raise ValueError('value must be delimited with `:` and consist '
1645 raise ValueError('value must be delimited with `:` and consist '
1646 'of <rev_type>:<rev>, got %s instead' % val)
1646 'of <rev_type>:<rev>, got %s instead' % val)
1647 self._landing_revision = val
1647 self._landing_revision = val
1648
1648
1649 @hybrid_property
1649 @hybrid_property
1650 def locked(self):
1650 def locked(self):
1651 if self._locked:
1651 if self._locked:
1652 user_id, timelocked, reason = self._locked.split(':')
1652 user_id, timelocked, reason = self._locked.split(':')
1653 lock_values = int(user_id), timelocked, reason
1653 lock_values = int(user_id), timelocked, reason
1654 else:
1654 else:
1655 lock_values = [None, None, None]
1655 lock_values = [None, None, None]
1656 return lock_values
1656 return lock_values
1657
1657
1658 @locked.setter
1658 @locked.setter
1659 def locked(self, val):
1659 def locked(self, val):
1660 if val and isinstance(val, (list, tuple)):
1660 if val and isinstance(val, (list, tuple)):
1661 self._locked = ':'.join(map(str, val))
1661 self._locked = ':'.join(map(str, val))
1662 else:
1662 else:
1663 self._locked = None
1663 self._locked = None
1664
1664
1665 @hybrid_property
1665 @hybrid_property
1666 def changeset_cache(self):
1666 def changeset_cache(self):
1667 from rhodecode.lib.vcs.backends.base import EmptyCommit
1667 from rhodecode.lib.vcs.backends.base import EmptyCommit
1668 dummy = EmptyCommit().__json__()
1668 dummy = EmptyCommit().__json__()
1669 if not self._changeset_cache:
1669 if not self._changeset_cache:
1670 return dummy
1670 return dummy
1671 try:
1671 try:
1672 return json.loads(self._changeset_cache)
1672 return json.loads(self._changeset_cache)
1673 except TypeError:
1673 except TypeError:
1674 return dummy
1674 return dummy
1675 except Exception:
1675 except Exception:
1676 log.error(traceback.format_exc())
1676 log.error(traceback.format_exc())
1677 return dummy
1677 return dummy
1678
1678
1679 @changeset_cache.setter
1679 @changeset_cache.setter
1680 def changeset_cache(self, val):
1680 def changeset_cache(self, val):
1681 try:
1681 try:
1682 self._changeset_cache = json.dumps(val)
1682 self._changeset_cache = json.dumps(val)
1683 except Exception:
1683 except Exception:
1684 log.error(traceback.format_exc())
1684 log.error(traceback.format_exc())
1685
1685
1686 @hybrid_property
1686 @hybrid_property
1687 def repo_name(self):
1687 def repo_name(self):
1688 return self._repo_name
1688 return self._repo_name
1689
1689
1690 @repo_name.setter
1690 @repo_name.setter
1691 def repo_name(self, value):
1691 def repo_name(self, value):
1692 self._repo_name = value
1692 self._repo_name = value
1693 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1693 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1694
1694
1695 @classmethod
1695 @classmethod
1696 def normalize_repo_name(cls, repo_name):
1696 def normalize_repo_name(cls, repo_name):
1697 """
1697 """
1698 Normalizes os specific repo_name to the format internally stored inside
1698 Normalizes os specific repo_name to the format internally stored inside
1699 database using URL_SEP
1699 database using URL_SEP
1700
1700
1701 :param cls:
1701 :param cls:
1702 :param repo_name:
1702 :param repo_name:
1703 """
1703 """
1704 return cls.NAME_SEP.join(repo_name.split(os.sep))
1704 return cls.NAME_SEP.join(repo_name.split(os.sep))
1705
1705
1706 @classmethod
1706 @classmethod
1707 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1707 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1708 session = Session()
1708 session = Session()
1709 q = session.query(cls).filter(cls.repo_name == repo_name)
1709 q = session.query(cls).filter(cls.repo_name == repo_name)
1710
1710
1711 if cache:
1711 if cache:
1712 if identity_cache:
1712 if identity_cache:
1713 val = cls.identity_cache(session, 'repo_name', repo_name)
1713 val = cls.identity_cache(session, 'repo_name', repo_name)
1714 if val:
1714 if val:
1715 return val
1715 return val
1716 else:
1716 else:
1717 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1717 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1718 q = q.options(
1718 q = q.options(
1719 FromCache("sql_cache_short", cache_key))
1719 FromCache("sql_cache_short", cache_key))
1720
1720
1721 return q.scalar()
1721 return q.scalar()
1722
1722
1723 @classmethod
1723 @classmethod
1724 def get_by_full_path(cls, repo_full_path):
1724 def get_by_full_path(cls, repo_full_path):
1725 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1725 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1726 repo_name = cls.normalize_repo_name(repo_name)
1726 repo_name = cls.normalize_repo_name(repo_name)
1727 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1727 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1728
1728
1729 @classmethod
1729 @classmethod
1730 def get_repo_forks(cls, repo_id):
1730 def get_repo_forks(cls, repo_id):
1731 return cls.query().filter(Repository.fork_id == repo_id)
1731 return cls.query().filter(Repository.fork_id == repo_id)
1732
1732
1733 @classmethod
1733 @classmethod
1734 def base_path(cls):
1734 def base_path(cls):
1735 """
1735 """
1736 Returns base path when all repos are stored
1736 Returns base path when all repos are stored
1737
1737
1738 :param cls:
1738 :param cls:
1739 """
1739 """
1740 q = Session().query(RhodeCodeUi)\
1740 q = Session().query(RhodeCodeUi)\
1741 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1741 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1742 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1742 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1743 return q.one().ui_value
1743 return q.one().ui_value
1744
1744
1745 @classmethod
1745 @classmethod
1746 def is_valid(cls, repo_name):
1746 def is_valid(cls, repo_name):
1747 """
1747 """
1748 returns True if given repo name is a valid filesystem repository
1748 returns True if given repo name is a valid filesystem repository
1749
1749
1750 :param cls:
1750 :param cls:
1751 :param repo_name:
1751 :param repo_name:
1752 """
1752 """
1753 from rhodecode.lib.utils import is_valid_repo
1753 from rhodecode.lib.utils import is_valid_repo
1754
1754
1755 return is_valid_repo(repo_name, cls.base_path())
1755 return is_valid_repo(repo_name, cls.base_path())
1756
1756
1757 @classmethod
1757 @classmethod
1758 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1758 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1759 case_insensitive=True):
1759 case_insensitive=True):
1760 q = Repository.query()
1760 q = Repository.query()
1761
1761
1762 if not isinstance(user_id, Optional):
1762 if not isinstance(user_id, Optional):
1763 q = q.filter(Repository.user_id == user_id)
1763 q = q.filter(Repository.user_id == user_id)
1764
1764
1765 if not isinstance(group_id, Optional):
1765 if not isinstance(group_id, Optional):
1766 q = q.filter(Repository.group_id == group_id)
1766 q = q.filter(Repository.group_id == group_id)
1767
1767
1768 if case_insensitive:
1768 if case_insensitive:
1769 q = q.order_by(func.lower(Repository.repo_name))
1769 q = q.order_by(func.lower(Repository.repo_name))
1770 else:
1770 else:
1771 q = q.order_by(Repository.repo_name)
1771 q = q.order_by(Repository.repo_name)
1772 return q.all()
1772 return q.all()
1773
1773
1774 @property
1774 @property
1775 def forks(self):
1775 def forks(self):
1776 """
1776 """
1777 Return forks of this repo
1777 Return forks of this repo
1778 """
1778 """
1779 return Repository.get_repo_forks(self.repo_id)
1779 return Repository.get_repo_forks(self.repo_id)
1780
1780
1781 @property
1781 @property
1782 def parent(self):
1782 def parent(self):
1783 """
1783 """
1784 Returns fork parent
1784 Returns fork parent
1785 """
1785 """
1786 return self.fork
1786 return self.fork
1787
1787
1788 @property
1788 @property
1789 def just_name(self):
1789 def just_name(self):
1790 return self.repo_name.split(self.NAME_SEP)[-1]
1790 return self.repo_name.split(self.NAME_SEP)[-1]
1791
1791
1792 @property
1792 @property
1793 def groups_with_parents(self):
1793 def groups_with_parents(self):
1794 groups = []
1794 groups = []
1795 if self.group is None:
1795 if self.group is None:
1796 return groups
1796 return groups
1797
1797
1798 cur_gr = self.group
1798 cur_gr = self.group
1799 groups.insert(0, cur_gr)
1799 groups.insert(0, cur_gr)
1800 while 1:
1800 while 1:
1801 gr = getattr(cur_gr, 'parent_group', None)
1801 gr = getattr(cur_gr, 'parent_group', None)
1802 cur_gr = cur_gr.parent_group
1802 cur_gr = cur_gr.parent_group
1803 if gr is None:
1803 if gr is None:
1804 break
1804 break
1805 groups.insert(0, gr)
1805 groups.insert(0, gr)
1806
1806
1807 return groups
1807 return groups
1808
1808
1809 @property
1809 @property
1810 def groups_and_repo(self):
1810 def groups_and_repo(self):
1811 return self.groups_with_parents, self
1811 return self.groups_with_parents, self
1812
1812
1813 @LazyProperty
1813 @LazyProperty
1814 def repo_path(self):
1814 def repo_path(self):
1815 """
1815 """
1816 Returns base full path for that repository means where it actually
1816 Returns base full path for that repository means where it actually
1817 exists on a filesystem
1817 exists on a filesystem
1818 """
1818 """
1819 q = Session().query(RhodeCodeUi).filter(
1819 q = Session().query(RhodeCodeUi).filter(
1820 RhodeCodeUi.ui_key == self.NAME_SEP)
1820 RhodeCodeUi.ui_key == self.NAME_SEP)
1821 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1821 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1822 return q.one().ui_value
1822 return q.one().ui_value
1823
1823
1824 @property
1824 @property
1825 def repo_full_path(self):
1825 def repo_full_path(self):
1826 p = [self.repo_path]
1826 p = [self.repo_path]
1827 # we need to split the name by / since this is how we store the
1827 # we need to split the name by / since this is how we store the
1828 # names in the database, but that eventually needs to be converted
1828 # names in the database, but that eventually needs to be converted
1829 # into a valid system path
1829 # into a valid system path
1830 p += self.repo_name.split(self.NAME_SEP)
1830 p += self.repo_name.split(self.NAME_SEP)
1831 return os.path.join(*map(safe_unicode, p))
1831 return os.path.join(*map(safe_unicode, p))
1832
1832
1833 @property
1833 @property
1834 def cache_keys(self):
1834 def cache_keys(self):
1835 """
1835 """
1836 Returns associated cache keys for that repo
1836 Returns associated cache keys for that repo
1837 """
1837 """
1838 return CacheKey.query()\
1838 return CacheKey.query()\
1839 .filter(CacheKey.cache_args == self.repo_name)\
1839 .filter(CacheKey.cache_args == self.repo_name)\
1840 .order_by(CacheKey.cache_key)\
1840 .order_by(CacheKey.cache_key)\
1841 .all()
1841 .all()
1842
1842
1843 def get_new_name(self, repo_name):
1843 def get_new_name(self, repo_name):
1844 """
1844 """
1845 returns new full repository name based on assigned group and new new
1845 returns new full repository name based on assigned group and new new
1846
1846
1847 :param group_name:
1847 :param group_name:
1848 """
1848 """
1849 path_prefix = self.group.full_path_splitted if self.group else []
1849 path_prefix = self.group.full_path_splitted if self.group else []
1850 return self.NAME_SEP.join(path_prefix + [repo_name])
1850 return self.NAME_SEP.join(path_prefix + [repo_name])
1851
1851
1852 @property
1852 @property
1853 def _config(self):
1853 def _config(self):
1854 """
1854 """
1855 Returns db based config object.
1855 Returns db based config object.
1856 """
1856 """
1857 from rhodecode.lib.utils import make_db_config
1857 from rhodecode.lib.utils import make_db_config
1858 return make_db_config(clear_session=False, repo=self)
1858 return make_db_config(clear_session=False, repo=self)
1859
1859
1860 def permissions(self, with_admins=True, with_owner=True):
1860 def permissions(self, with_admins=True, with_owner=True):
1861 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1861 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1862 q = q.options(joinedload(UserRepoToPerm.repository),
1862 q = q.options(joinedload(UserRepoToPerm.repository),
1863 joinedload(UserRepoToPerm.user),
1863 joinedload(UserRepoToPerm.user),
1864 joinedload(UserRepoToPerm.permission),)
1864 joinedload(UserRepoToPerm.permission),)
1865
1865
1866 # get owners and admins and permissions. We do a trick of re-writing
1866 # get owners and admins and permissions. We do a trick of re-writing
1867 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1867 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1868 # has a global reference and changing one object propagates to all
1868 # has a global reference and changing one object propagates to all
1869 # others. This means if admin is also an owner admin_row that change
1869 # others. This means if admin is also an owner admin_row that change
1870 # would propagate to both objects
1870 # would propagate to both objects
1871 perm_rows = []
1871 perm_rows = []
1872 for _usr in q.all():
1872 for _usr in q.all():
1873 usr = AttributeDict(_usr.user.get_dict())
1873 usr = AttributeDict(_usr.user.get_dict())
1874 usr.permission = _usr.permission.permission_name
1874 usr.permission = _usr.permission.permission_name
1875 perm_rows.append(usr)
1875 perm_rows.append(usr)
1876
1876
1877 # filter the perm rows by 'default' first and then sort them by
1877 # filter the perm rows by 'default' first and then sort them by
1878 # admin,write,read,none permissions sorted again alphabetically in
1878 # admin,write,read,none permissions sorted again alphabetically in
1879 # each group
1879 # each group
1880 perm_rows = sorted(perm_rows, key=display_user_sort)
1880 perm_rows = sorted(perm_rows, key=display_user_sort)
1881
1881
1882 _admin_perm = 'repository.admin'
1882 _admin_perm = 'repository.admin'
1883 owner_row = []
1883 owner_row = []
1884 if with_owner:
1884 if with_owner:
1885 usr = AttributeDict(self.user.get_dict())
1885 usr = AttributeDict(self.user.get_dict())
1886 usr.owner_row = True
1886 usr.owner_row = True
1887 usr.permission = _admin_perm
1887 usr.permission = _admin_perm
1888 owner_row.append(usr)
1888 owner_row.append(usr)
1889
1889
1890 super_admin_rows = []
1890 super_admin_rows = []
1891 if with_admins:
1891 if with_admins:
1892 for usr in User.get_all_super_admins():
1892 for usr in User.get_all_super_admins():
1893 # if this admin is also owner, don't double the record
1893 # if this admin is also owner, don't double the record
1894 if usr.user_id == owner_row[0].user_id:
1894 if usr.user_id == owner_row[0].user_id:
1895 owner_row[0].admin_row = True
1895 owner_row[0].admin_row = True
1896 else:
1896 else:
1897 usr = AttributeDict(usr.get_dict())
1897 usr = AttributeDict(usr.get_dict())
1898 usr.admin_row = True
1898 usr.admin_row = True
1899 usr.permission = _admin_perm
1899 usr.permission = _admin_perm
1900 super_admin_rows.append(usr)
1900 super_admin_rows.append(usr)
1901
1901
1902 return super_admin_rows + owner_row + perm_rows
1902 return super_admin_rows + owner_row + perm_rows
1903
1903
1904 def permission_user_groups(self):
1904 def permission_user_groups(self):
1905 q = UserGroupRepoToPerm.query().filter(
1905 q = UserGroupRepoToPerm.query().filter(
1906 UserGroupRepoToPerm.repository == self)
1906 UserGroupRepoToPerm.repository == self)
1907 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1907 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1908 joinedload(UserGroupRepoToPerm.users_group),
1908 joinedload(UserGroupRepoToPerm.users_group),
1909 joinedload(UserGroupRepoToPerm.permission),)
1909 joinedload(UserGroupRepoToPerm.permission),)
1910
1910
1911 perm_rows = []
1911 perm_rows = []
1912 for _user_group in q.all():
1912 for _user_group in q.all():
1913 usr = AttributeDict(_user_group.users_group.get_dict())
1913 usr = AttributeDict(_user_group.users_group.get_dict())
1914 usr.permission = _user_group.permission.permission_name
1914 usr.permission = _user_group.permission.permission_name
1915 perm_rows.append(usr)
1915 perm_rows.append(usr)
1916
1916
1917 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1917 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1918 return perm_rows
1918 return perm_rows
1919
1919
1920 def get_api_data(self, include_secrets=False):
1920 def get_api_data(self, include_secrets=False):
1921 """
1921 """
1922 Common function for generating repo api data
1922 Common function for generating repo api data
1923
1923
1924 :param include_secrets: See :meth:`User.get_api_data`.
1924 :param include_secrets: See :meth:`User.get_api_data`.
1925
1925
1926 """
1926 """
1927 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1927 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1928 # move this methods on models level.
1928 # move this methods on models level.
1929 from rhodecode.model.settings import SettingsModel
1929 from rhodecode.model.settings import SettingsModel
1930 from rhodecode.model.repo import RepoModel
1930 from rhodecode.model.repo import RepoModel
1931
1931
1932 repo = self
1932 repo = self
1933 _user_id, _time, _reason = self.locked
1933 _user_id, _time, _reason = self.locked
1934
1934
1935 data = {
1935 data = {
1936 'repo_id': repo.repo_id,
1936 'repo_id': repo.repo_id,
1937 'repo_name': repo.repo_name,
1937 'repo_name': repo.repo_name,
1938 'repo_type': repo.repo_type,
1938 'repo_type': repo.repo_type,
1939 'clone_uri': repo.clone_uri or '',
1939 'clone_uri': repo.clone_uri or '',
1940 'url': RepoModel().get_url(self),
1940 'url': RepoModel().get_url(self),
1941 'private': repo.private,
1941 'private': repo.private,
1942 'created_on': repo.created_on,
1942 'created_on': repo.created_on,
1943 'description': repo.description_safe,
1943 'description': repo.description_safe,
1944 'landing_rev': repo.landing_rev,
1944 'landing_rev': repo.landing_rev,
1945 'owner': repo.user.username,
1945 'owner': repo.user.username,
1946 'fork_of': repo.fork.repo_name if repo.fork else None,
1946 'fork_of': repo.fork.repo_name if repo.fork else None,
1947 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1947 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1948 'enable_statistics': repo.enable_statistics,
1948 'enable_statistics': repo.enable_statistics,
1949 'enable_locking': repo.enable_locking,
1949 'enable_locking': repo.enable_locking,
1950 'enable_downloads': repo.enable_downloads,
1950 'enable_downloads': repo.enable_downloads,
1951 'last_changeset': repo.changeset_cache,
1951 'last_changeset': repo.changeset_cache,
1952 'locked_by': User.get(_user_id).get_api_data(
1952 'locked_by': User.get(_user_id).get_api_data(
1953 include_secrets=include_secrets) if _user_id else None,
1953 include_secrets=include_secrets) if _user_id else None,
1954 'locked_date': time_to_datetime(_time) if _time else None,
1954 'locked_date': time_to_datetime(_time) if _time else None,
1955 'lock_reason': _reason if _reason else None,
1955 'lock_reason': _reason if _reason else None,
1956 }
1956 }
1957
1957
1958 # TODO: mikhail: should be per-repo settings here
1958 # TODO: mikhail: should be per-repo settings here
1959 rc_config = SettingsModel().get_all_settings()
1959 rc_config = SettingsModel().get_all_settings()
1960 repository_fields = str2bool(
1960 repository_fields = str2bool(
1961 rc_config.get('rhodecode_repository_fields'))
1961 rc_config.get('rhodecode_repository_fields'))
1962 if repository_fields:
1962 if repository_fields:
1963 for f in self.extra_fields:
1963 for f in self.extra_fields:
1964 data[f.field_key_prefixed] = f.field_value
1964 data[f.field_key_prefixed] = f.field_value
1965
1965
1966 return data
1966 return data
1967
1967
1968 @classmethod
1968 @classmethod
1969 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1969 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1970 if not lock_time:
1970 if not lock_time:
1971 lock_time = time.time()
1971 lock_time = time.time()
1972 if not lock_reason:
1972 if not lock_reason:
1973 lock_reason = cls.LOCK_AUTOMATIC
1973 lock_reason = cls.LOCK_AUTOMATIC
1974 repo.locked = [user_id, lock_time, lock_reason]
1974 repo.locked = [user_id, lock_time, lock_reason]
1975 Session().add(repo)
1975 Session().add(repo)
1976 Session().commit()
1976 Session().commit()
1977
1977
1978 @classmethod
1978 @classmethod
1979 def unlock(cls, repo):
1979 def unlock(cls, repo):
1980 repo.locked = None
1980 repo.locked = None
1981 Session().add(repo)
1981 Session().add(repo)
1982 Session().commit()
1982 Session().commit()
1983
1983
1984 @classmethod
1984 @classmethod
1985 def getlock(cls, repo):
1985 def getlock(cls, repo):
1986 return repo.locked
1986 return repo.locked
1987
1987
1988 def is_user_lock(self, user_id):
1988 def is_user_lock(self, user_id):
1989 if self.lock[0]:
1989 if self.lock[0]:
1990 lock_user_id = safe_int(self.lock[0])
1990 lock_user_id = safe_int(self.lock[0])
1991 user_id = safe_int(user_id)
1991 user_id = safe_int(user_id)
1992 # both are ints, and they are equal
1992 # both are ints, and they are equal
1993 return all([lock_user_id, user_id]) and lock_user_id == user_id
1993 return all([lock_user_id, user_id]) and lock_user_id == user_id
1994
1994
1995 return False
1995 return False
1996
1996
1997 def get_locking_state(self, action, user_id, only_when_enabled=True):
1997 def get_locking_state(self, action, user_id, only_when_enabled=True):
1998 """
1998 """
1999 Checks locking on this repository, if locking is enabled and lock is
1999 Checks locking on this repository, if locking is enabled and lock is
2000 present returns a tuple of make_lock, locked, locked_by.
2000 present returns a tuple of make_lock, locked, locked_by.
2001 make_lock can have 3 states None (do nothing) True, make lock
2001 make_lock can have 3 states None (do nothing) True, make lock
2002 False release lock, This value is later propagated to hooks, which
2002 False release lock, This value is later propagated to hooks, which
2003 do the locking. Think about this as signals passed to hooks what to do.
2003 do the locking. Think about this as signals passed to hooks what to do.
2004
2004
2005 """
2005 """
2006 # TODO: johbo: This is part of the business logic and should be moved
2006 # TODO: johbo: This is part of the business logic and should be moved
2007 # into the RepositoryModel.
2007 # into the RepositoryModel.
2008
2008
2009 if action not in ('push', 'pull'):
2009 if action not in ('push', 'pull'):
2010 raise ValueError("Invalid action value: %s" % repr(action))
2010 raise ValueError("Invalid action value: %s" % repr(action))
2011
2011
2012 # defines if locked error should be thrown to user
2012 # defines if locked error should be thrown to user
2013 currently_locked = False
2013 currently_locked = False
2014 # defines if new lock should be made, tri-state
2014 # defines if new lock should be made, tri-state
2015 make_lock = None
2015 make_lock = None
2016 repo = self
2016 repo = self
2017 user = User.get(user_id)
2017 user = User.get(user_id)
2018
2018
2019 lock_info = repo.locked
2019 lock_info = repo.locked
2020
2020
2021 if repo and (repo.enable_locking or not only_when_enabled):
2021 if repo and (repo.enable_locking or not only_when_enabled):
2022 if action == 'push':
2022 if action == 'push':
2023 # check if it's already locked !, if it is compare users
2023 # check if it's already locked !, if it is compare users
2024 locked_by_user_id = lock_info[0]
2024 locked_by_user_id = lock_info[0]
2025 if user.user_id == locked_by_user_id:
2025 if user.user_id == locked_by_user_id:
2026 log.debug(
2026 log.debug(
2027 'Got `push` action from user %s, now unlocking', user)
2027 'Got `push` action from user %s, now unlocking', user)
2028 # unlock if we have push from user who locked
2028 # unlock if we have push from user who locked
2029 make_lock = False
2029 make_lock = False
2030 else:
2030 else:
2031 # we're not the same user who locked, ban with
2031 # we're not the same user who locked, ban with
2032 # code defined in settings (default is 423 HTTP Locked) !
2032 # code defined in settings (default is 423 HTTP Locked) !
2033 log.debug('Repo %s is currently locked by %s', repo, user)
2033 log.debug('Repo %s is currently locked by %s', repo, user)
2034 currently_locked = True
2034 currently_locked = True
2035 elif action == 'pull':
2035 elif action == 'pull':
2036 # [0] user [1] date
2036 # [0] user [1] date
2037 if lock_info[0] and lock_info[1]:
2037 if lock_info[0] and lock_info[1]:
2038 log.debug('Repo %s is currently locked by %s', repo, user)
2038 log.debug('Repo %s is currently locked by %s', repo, user)
2039 currently_locked = True
2039 currently_locked = True
2040 else:
2040 else:
2041 log.debug('Setting lock on repo %s by %s', repo, user)
2041 log.debug('Setting lock on repo %s by %s', repo, user)
2042 make_lock = True
2042 make_lock = True
2043
2043
2044 else:
2044 else:
2045 log.debug('Repository %s do not have locking enabled', repo)
2045 log.debug('Repository %s do not have locking enabled', repo)
2046
2046
2047 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2047 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2048 make_lock, currently_locked, lock_info)
2048 make_lock, currently_locked, lock_info)
2049
2049
2050 from rhodecode.lib.auth import HasRepoPermissionAny
2050 from rhodecode.lib.auth import HasRepoPermissionAny
2051 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2051 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2052 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2052 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2053 # if we don't have at least write permission we cannot make a lock
2053 # if we don't have at least write permission we cannot make a lock
2054 log.debug('lock state reset back to FALSE due to lack '
2054 log.debug('lock state reset back to FALSE due to lack '
2055 'of at least read permission')
2055 'of at least read permission')
2056 make_lock = False
2056 make_lock = False
2057
2057
2058 return make_lock, currently_locked, lock_info
2058 return make_lock, currently_locked, lock_info
2059
2059
2060 @property
2060 @property
2061 def last_db_change(self):
2061 def last_db_change(self):
2062 return self.updated_on
2062 return self.updated_on
2063
2063
2064 @property
2064 @property
2065 def clone_uri_hidden(self):
2065 def clone_uri_hidden(self):
2066 clone_uri = self.clone_uri
2066 clone_uri = self.clone_uri
2067 if clone_uri:
2067 if clone_uri:
2068 import urlobject
2068 import urlobject
2069 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2069 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2070 if url_obj.password:
2070 if url_obj.password:
2071 clone_uri = url_obj.with_password('*****')
2071 clone_uri = url_obj.with_password('*****')
2072 return clone_uri
2072 return clone_uri
2073
2073
2074 def clone_url(self, **override):
2074 def clone_url(self, **override):
2075 from rhodecode.model.settings import SettingsModel
2075 from rhodecode.model.settings import SettingsModel
2076
2076
2077 uri_tmpl = None
2077 uri_tmpl = None
2078 if 'with_id' in override:
2078 if 'with_id' in override:
2079 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2079 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2080 del override['with_id']
2080 del override['with_id']
2081
2081
2082 if 'uri_tmpl' in override:
2082 if 'uri_tmpl' in override:
2083 uri_tmpl = override['uri_tmpl']
2083 uri_tmpl = override['uri_tmpl']
2084 del override['uri_tmpl']
2084 del override['uri_tmpl']
2085
2085
2086 # we didn't override our tmpl from **overrides
2086 # we didn't override our tmpl from **overrides
2087 if not uri_tmpl:
2087 if not uri_tmpl:
2088 rc_config = SettingsModel().get_all_settings(cache=True)
2088 rc_config = SettingsModel().get_all_settings(cache=True)
2089 uri_tmpl = rc_config.get(
2089 uri_tmpl = rc_config.get(
2090 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2090 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2091
2091
2092 request = get_current_request()
2092 request = get_current_request()
2093 return get_clone_url(request=request,
2093 return get_clone_url(request=request,
2094 uri_tmpl=uri_tmpl,
2094 uri_tmpl=uri_tmpl,
2095 repo_name=self.repo_name,
2095 repo_name=self.repo_name,
2096 repo_id=self.repo_id, **override)
2096 repo_id=self.repo_id, **override)
2097
2097
2098 def set_state(self, state):
2098 def set_state(self, state):
2099 self.repo_state = state
2099 self.repo_state = state
2100 Session().add(self)
2100 Session().add(self)
2101 #==========================================================================
2101 #==========================================================================
2102 # SCM PROPERTIES
2102 # SCM PROPERTIES
2103 #==========================================================================
2103 #==========================================================================
2104
2104
2105 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2105 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2106 return get_commit_safe(
2106 return get_commit_safe(
2107 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2107 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2108
2108
2109 def get_changeset(self, rev=None, pre_load=None):
2109 def get_changeset(self, rev=None, pre_load=None):
2110 warnings.warn("Use get_commit", DeprecationWarning)
2110 warnings.warn("Use get_commit", DeprecationWarning)
2111 commit_id = None
2111 commit_id = None
2112 commit_idx = None
2112 commit_idx = None
2113 if isinstance(rev, basestring):
2113 if isinstance(rev, basestring):
2114 commit_id = rev
2114 commit_id = rev
2115 else:
2115 else:
2116 commit_idx = rev
2116 commit_idx = rev
2117 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2117 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2118 pre_load=pre_load)
2118 pre_load=pre_load)
2119
2119
2120 def get_landing_commit(self):
2120 def get_landing_commit(self):
2121 """
2121 """
2122 Returns landing commit, or if that doesn't exist returns the tip
2122 Returns landing commit, or if that doesn't exist returns the tip
2123 """
2123 """
2124 _rev_type, _rev = self.landing_rev
2124 _rev_type, _rev = self.landing_rev
2125 commit = self.get_commit(_rev)
2125 commit = self.get_commit(_rev)
2126 if isinstance(commit, EmptyCommit):
2126 if isinstance(commit, EmptyCommit):
2127 return self.get_commit()
2127 return self.get_commit()
2128 return commit
2128 return commit
2129
2129
2130 def update_commit_cache(self, cs_cache=None, config=None):
2130 def update_commit_cache(self, cs_cache=None, config=None):
2131 """
2131 """
2132 Update cache of last changeset for repository, keys should be::
2132 Update cache of last changeset for repository, keys should be::
2133
2133
2134 short_id
2134 short_id
2135 raw_id
2135 raw_id
2136 revision
2136 revision
2137 parents
2137 parents
2138 message
2138 message
2139 date
2139 date
2140 author
2140 author
2141
2141
2142 :param cs_cache:
2142 :param cs_cache:
2143 """
2143 """
2144 from rhodecode.lib.vcs.backends.base import BaseChangeset
2144 from rhodecode.lib.vcs.backends.base import BaseChangeset
2145 if cs_cache is None:
2145 if cs_cache is None:
2146 # use no-cache version here
2146 # use no-cache version here
2147 scm_repo = self.scm_instance(cache=False, config=config)
2147 scm_repo = self.scm_instance(cache=False, config=config)
2148 if scm_repo:
2148 if scm_repo:
2149 cs_cache = scm_repo.get_commit(
2149 cs_cache = scm_repo.get_commit(
2150 pre_load=["author", "date", "message", "parents"])
2150 pre_load=["author", "date", "message", "parents"])
2151 else:
2151 else:
2152 cs_cache = EmptyCommit()
2152 cs_cache = EmptyCommit()
2153
2153
2154 if isinstance(cs_cache, BaseChangeset):
2154 if isinstance(cs_cache, BaseChangeset):
2155 cs_cache = cs_cache.__json__()
2155 cs_cache = cs_cache.__json__()
2156
2156
2157 def is_outdated(new_cs_cache):
2157 def is_outdated(new_cs_cache):
2158 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2158 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2159 new_cs_cache['revision'] != self.changeset_cache['revision']):
2159 new_cs_cache['revision'] != self.changeset_cache['revision']):
2160 return True
2160 return True
2161 return False
2161 return False
2162
2162
2163 # check if we have maybe already latest cached revision
2163 # check if we have maybe already latest cached revision
2164 if is_outdated(cs_cache) or not self.changeset_cache:
2164 if is_outdated(cs_cache) or not self.changeset_cache:
2165 _default = datetime.datetime.fromtimestamp(0)
2165 _default = datetime.datetime.fromtimestamp(0)
2166 last_change = cs_cache.get('date') or _default
2166 last_change = cs_cache.get('date') or _default
2167 log.debug('updated repo %s with new cs cache %s',
2167 log.debug('updated repo %s with new cs cache %s',
2168 self.repo_name, cs_cache)
2168 self.repo_name, cs_cache)
2169 self.updated_on = last_change
2169 self.updated_on = last_change
2170 self.changeset_cache = cs_cache
2170 self.changeset_cache = cs_cache
2171 Session().add(self)
2171 Session().add(self)
2172 Session().commit()
2172 Session().commit()
2173 else:
2173 else:
2174 log.debug('Skipping update_commit_cache for repo:`%s` '
2174 log.debug('Skipping update_commit_cache for repo:`%s` '
2175 'commit already with latest changes', self.repo_name)
2175 'commit already with latest changes', self.repo_name)
2176
2176
2177 @property
2177 @property
2178 def tip(self):
2178 def tip(self):
2179 return self.get_commit('tip')
2179 return self.get_commit('tip')
2180
2180
2181 @property
2181 @property
2182 def author(self):
2182 def author(self):
2183 return self.tip.author
2183 return self.tip.author
2184
2184
2185 @property
2185 @property
2186 def last_change(self):
2186 def last_change(self):
2187 return self.scm_instance().last_change
2187 return self.scm_instance().last_change
2188
2188
2189 def get_comments(self, revisions=None):
2189 def get_comments(self, revisions=None):
2190 """
2190 """
2191 Returns comments for this repository grouped by revisions
2191 Returns comments for this repository grouped by revisions
2192
2192
2193 :param revisions: filter query by revisions only
2193 :param revisions: filter query by revisions only
2194 """
2194 """
2195 cmts = ChangesetComment.query()\
2195 cmts = ChangesetComment.query()\
2196 .filter(ChangesetComment.repo == self)
2196 .filter(ChangesetComment.repo == self)
2197 if revisions:
2197 if revisions:
2198 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2198 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2199 grouped = collections.defaultdict(list)
2199 grouped = collections.defaultdict(list)
2200 for cmt in cmts.all():
2200 for cmt in cmts.all():
2201 grouped[cmt.revision].append(cmt)
2201 grouped[cmt.revision].append(cmt)
2202 return grouped
2202 return grouped
2203
2203
2204 def statuses(self, revisions=None):
2204 def statuses(self, revisions=None):
2205 """
2205 """
2206 Returns statuses for this repository
2206 Returns statuses for this repository
2207
2207
2208 :param revisions: list of revisions to get statuses for
2208 :param revisions: list of revisions to get statuses for
2209 """
2209 """
2210 statuses = ChangesetStatus.query()\
2210 statuses = ChangesetStatus.query()\
2211 .filter(ChangesetStatus.repo == self)\
2211 .filter(ChangesetStatus.repo == self)\
2212 .filter(ChangesetStatus.version == 0)
2212 .filter(ChangesetStatus.version == 0)
2213
2213
2214 if revisions:
2214 if revisions:
2215 # Try doing the filtering in chunks to avoid hitting limits
2215 # Try doing the filtering in chunks to avoid hitting limits
2216 size = 500
2216 size = 500
2217 status_results = []
2217 status_results = []
2218 for chunk in xrange(0, len(revisions), size):
2218 for chunk in xrange(0, len(revisions), size):
2219 status_results += statuses.filter(
2219 status_results += statuses.filter(
2220 ChangesetStatus.revision.in_(
2220 ChangesetStatus.revision.in_(
2221 revisions[chunk: chunk+size])
2221 revisions[chunk: chunk+size])
2222 ).all()
2222 ).all()
2223 else:
2223 else:
2224 status_results = statuses.all()
2224 status_results = statuses.all()
2225
2225
2226 grouped = {}
2226 grouped = {}
2227
2227
2228 # maybe we have open new pullrequest without a status?
2228 # maybe we have open new pullrequest without a status?
2229 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2229 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2230 status_lbl = ChangesetStatus.get_status_lbl(stat)
2230 status_lbl = ChangesetStatus.get_status_lbl(stat)
2231 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2231 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2232 for rev in pr.revisions:
2232 for rev in pr.revisions:
2233 pr_id = pr.pull_request_id
2233 pr_id = pr.pull_request_id
2234 pr_repo = pr.target_repo.repo_name
2234 pr_repo = pr.target_repo.repo_name
2235 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2235 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2236
2236
2237 for stat in status_results:
2237 for stat in status_results:
2238 pr_id = pr_repo = None
2238 pr_id = pr_repo = None
2239 if stat.pull_request:
2239 if stat.pull_request:
2240 pr_id = stat.pull_request.pull_request_id
2240 pr_id = stat.pull_request.pull_request_id
2241 pr_repo = stat.pull_request.target_repo.repo_name
2241 pr_repo = stat.pull_request.target_repo.repo_name
2242 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2242 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2243 pr_id, pr_repo]
2243 pr_id, pr_repo]
2244 return grouped
2244 return grouped
2245
2245
2246 # ==========================================================================
2246 # ==========================================================================
2247 # SCM CACHE INSTANCE
2247 # SCM CACHE INSTANCE
2248 # ==========================================================================
2248 # ==========================================================================
2249
2249
2250 def scm_instance(self, **kwargs):
2250 def scm_instance(self, **kwargs):
2251 import rhodecode
2251 import rhodecode
2252
2252
2253 # Passing a config will not hit the cache currently only used
2253 # Passing a config will not hit the cache currently only used
2254 # for repo2dbmapper
2254 # for repo2dbmapper
2255 config = kwargs.pop('config', None)
2255 config = kwargs.pop('config', None)
2256 cache = kwargs.pop('cache', None)
2256 cache = kwargs.pop('cache', None)
2257 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2257 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2258 # if cache is NOT defined use default global, else we have a full
2258 # if cache is NOT defined use default global, else we have a full
2259 # control over cache behaviour
2259 # control over cache behaviour
2260 if cache is None and full_cache and not config:
2260 if cache is None and full_cache and not config:
2261 return self._get_instance_cached()
2261 return self._get_instance_cached()
2262 return self._get_instance(cache=bool(cache), config=config)
2262 return self._get_instance(cache=bool(cache), config=config)
2263
2263
2264 def _get_instance_cached(self):
2264 def _get_instance_cached(self):
2265 self._get_instance()
2265 self._get_instance()
2266
2266
2267 def _get_instance(self, cache=True, config=None):
2267 def _get_instance(self, cache=True, config=None):
2268 config = config or self._config
2268 config = config or self._config
2269 custom_wire = {
2269 custom_wire = {
2270 'cache': cache # controls the vcs.remote cache
2270 'cache': cache # controls the vcs.remote cache
2271 }
2271 }
2272 repo = get_vcs_instance(
2272 repo = get_vcs_instance(
2273 repo_path=safe_str(self.repo_full_path),
2273 repo_path=safe_str(self.repo_full_path),
2274 config=config,
2274 config=config,
2275 with_wire=custom_wire,
2275 with_wire=custom_wire,
2276 create=False,
2276 create=False,
2277 _vcs_alias=self.repo_type)
2277 _vcs_alias=self.repo_type)
2278
2278
2279 return repo
2279 return repo
2280
2280
2281 def __json__(self):
2281 def __json__(self):
2282 return {'landing_rev': self.landing_rev}
2282 return {'landing_rev': self.landing_rev}
2283
2283
2284 def get_dict(self):
2284 def get_dict(self):
2285
2285
2286 # Since we transformed `repo_name` to a hybrid property, we need to
2286 # Since we transformed `repo_name` to a hybrid property, we need to
2287 # keep compatibility with the code which uses `repo_name` field.
2287 # keep compatibility with the code which uses `repo_name` field.
2288
2288
2289 result = super(Repository, self).get_dict()
2289 result = super(Repository, self).get_dict()
2290 result['repo_name'] = result.pop('_repo_name', None)
2290 result['repo_name'] = result.pop('_repo_name', None)
2291 return result
2291 return result
2292
2292
2293
2293
2294 class RepoGroup(Base, BaseModel):
2294 class RepoGroup(Base, BaseModel):
2295 __tablename__ = 'groups'
2295 __tablename__ = 'groups'
2296 __table_args__ = (
2296 __table_args__ = (
2297 UniqueConstraint('group_name', 'group_parent_id'),
2297 UniqueConstraint('group_name', 'group_parent_id'),
2298 CheckConstraint('group_id != group_parent_id'),
2298 CheckConstraint('group_id != group_parent_id'),
2299 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2299 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2300 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2300 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2301 )
2301 )
2302 __mapper_args__ = {'order_by': 'group_name'}
2302 __mapper_args__ = {'order_by': 'group_name'}
2303
2303
2304 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2304 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2305
2305
2306 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2306 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2307 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2307 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2308 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2308 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2309 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2309 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2310 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2310 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2312 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2312 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2313 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2313 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2314 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2314 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2315
2315
2316 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2316 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2317 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2317 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2318 parent_group = relationship('RepoGroup', remote_side=group_id)
2318 parent_group = relationship('RepoGroup', remote_side=group_id)
2319 user = relationship('User')
2319 user = relationship('User')
2320 integrations = relationship('Integration',
2320 integrations = relationship('Integration',
2321 cascade="all, delete, delete-orphan")
2321 cascade="all, delete, delete-orphan")
2322
2322
2323 def __init__(self, group_name='', parent_group=None):
2323 def __init__(self, group_name='', parent_group=None):
2324 self.group_name = group_name
2324 self.group_name = group_name
2325 self.parent_group = parent_group
2325 self.parent_group = parent_group
2326
2326
2327 def __unicode__(self):
2327 def __unicode__(self):
2328 return u"<%s('id:%s:%s')>" % (
2328 return u"<%s('id:%s:%s')>" % (
2329 self.__class__.__name__, self.group_id, self.group_name)
2329 self.__class__.__name__, self.group_id, self.group_name)
2330
2330
2331 @hybrid_property
2331 @hybrid_property
2332 def description_safe(self):
2332 def description_safe(self):
2333 from rhodecode.lib import helpers as h
2333 from rhodecode.lib import helpers as h
2334 return h.escape(self.group_description)
2334 return h.escape(self.group_description)
2335
2335
2336 @classmethod
2336 @classmethod
2337 def _generate_choice(cls, repo_group):
2337 def _generate_choice(cls, repo_group):
2338 from webhelpers.html import literal as _literal
2338 from webhelpers.html import literal as _literal
2339 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2339 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2340 return repo_group.group_id, _name(repo_group.full_path_splitted)
2340 return repo_group.group_id, _name(repo_group.full_path_splitted)
2341
2341
2342 @classmethod
2342 @classmethod
2343 def groups_choices(cls, groups=None, show_empty_group=True):
2343 def groups_choices(cls, groups=None, show_empty_group=True):
2344 if not groups:
2344 if not groups:
2345 groups = cls.query().all()
2345 groups = cls.query().all()
2346
2346
2347 repo_groups = []
2347 repo_groups = []
2348 if show_empty_group:
2348 if show_empty_group:
2349 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2349 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2350
2350
2351 repo_groups.extend([cls._generate_choice(x) for x in groups])
2351 repo_groups.extend([cls._generate_choice(x) for x in groups])
2352
2352
2353 repo_groups = sorted(
2353 repo_groups = sorted(
2354 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2354 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2355 return repo_groups
2355 return repo_groups
2356
2356
2357 @classmethod
2357 @classmethod
2358 def url_sep(cls):
2358 def url_sep(cls):
2359 return URL_SEP
2359 return URL_SEP
2360
2360
2361 @classmethod
2361 @classmethod
2362 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2362 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2363 if case_insensitive:
2363 if case_insensitive:
2364 gr = cls.query().filter(func.lower(cls.group_name)
2364 gr = cls.query().filter(func.lower(cls.group_name)
2365 == func.lower(group_name))
2365 == func.lower(group_name))
2366 else:
2366 else:
2367 gr = cls.query().filter(cls.group_name == group_name)
2367 gr = cls.query().filter(cls.group_name == group_name)
2368 if cache:
2368 if cache:
2369 name_key = _hash_key(group_name)
2369 name_key = _hash_key(group_name)
2370 gr = gr.options(
2370 gr = gr.options(
2371 FromCache("sql_cache_short", "get_group_%s" % name_key))
2371 FromCache("sql_cache_short", "get_group_%s" % name_key))
2372 return gr.scalar()
2372 return gr.scalar()
2373
2373
2374 @classmethod
2374 @classmethod
2375 def get_user_personal_repo_group(cls, user_id):
2375 def get_user_personal_repo_group(cls, user_id):
2376 user = User.get(user_id)
2376 user = User.get(user_id)
2377 if user.username == User.DEFAULT_USER:
2377 if user.username == User.DEFAULT_USER:
2378 return None
2378 return None
2379
2379
2380 return cls.query()\
2380 return cls.query()\
2381 .filter(cls.personal == true()) \
2381 .filter(cls.personal == true()) \
2382 .filter(cls.user == user).scalar()
2382 .filter(cls.user == user).scalar()
2383
2383
2384 @classmethod
2384 @classmethod
2385 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2385 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2386 case_insensitive=True):
2386 case_insensitive=True):
2387 q = RepoGroup.query()
2387 q = RepoGroup.query()
2388
2388
2389 if not isinstance(user_id, Optional):
2389 if not isinstance(user_id, Optional):
2390 q = q.filter(RepoGroup.user_id == user_id)
2390 q = q.filter(RepoGroup.user_id == user_id)
2391
2391
2392 if not isinstance(group_id, Optional):
2392 if not isinstance(group_id, Optional):
2393 q = q.filter(RepoGroup.group_parent_id == group_id)
2393 q = q.filter(RepoGroup.group_parent_id == group_id)
2394
2394
2395 if case_insensitive:
2395 if case_insensitive:
2396 q = q.order_by(func.lower(RepoGroup.group_name))
2396 q = q.order_by(func.lower(RepoGroup.group_name))
2397 else:
2397 else:
2398 q = q.order_by(RepoGroup.group_name)
2398 q = q.order_by(RepoGroup.group_name)
2399 return q.all()
2399 return q.all()
2400
2400
2401 @property
2401 @property
2402 def parents(self):
2402 def parents(self):
2403 parents_recursion_limit = 10
2403 parents_recursion_limit = 10
2404 groups = []
2404 groups = []
2405 if self.parent_group is None:
2405 if self.parent_group is None:
2406 return groups
2406 return groups
2407 cur_gr = self.parent_group
2407 cur_gr = self.parent_group
2408 groups.insert(0, cur_gr)
2408 groups.insert(0, cur_gr)
2409 cnt = 0
2409 cnt = 0
2410 while 1:
2410 while 1:
2411 cnt += 1
2411 cnt += 1
2412 gr = getattr(cur_gr, 'parent_group', None)
2412 gr = getattr(cur_gr, 'parent_group', None)
2413 cur_gr = cur_gr.parent_group
2413 cur_gr = cur_gr.parent_group
2414 if gr is None:
2414 if gr is None:
2415 break
2415 break
2416 if cnt == parents_recursion_limit:
2416 if cnt == parents_recursion_limit:
2417 # this will prevent accidental infinit loops
2417 # this will prevent accidental infinit loops
2418 log.error('more than %s parents found for group %s, stopping '
2418 log.error('more than %s parents found for group %s, stopping '
2419 'recursive parent fetching', parents_recursion_limit, self)
2419 'recursive parent fetching', parents_recursion_limit, self)
2420 break
2420 break
2421
2421
2422 groups.insert(0, gr)
2422 groups.insert(0, gr)
2423 return groups
2423 return groups
2424
2424
2425 @property
2425 @property
2426 def last_db_change(self):
2426 def last_db_change(self):
2427 return self.updated_on
2427 return self.updated_on
2428
2428
2429 @property
2429 @property
2430 def children(self):
2430 def children(self):
2431 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2431 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2432
2432
2433 @property
2433 @property
2434 def name(self):
2434 def name(self):
2435 return self.group_name.split(RepoGroup.url_sep())[-1]
2435 return self.group_name.split(RepoGroup.url_sep())[-1]
2436
2436
2437 @property
2437 @property
2438 def full_path(self):
2438 def full_path(self):
2439 return self.group_name
2439 return self.group_name
2440
2440
2441 @property
2441 @property
2442 def full_path_splitted(self):
2442 def full_path_splitted(self):
2443 return self.group_name.split(RepoGroup.url_sep())
2443 return self.group_name.split(RepoGroup.url_sep())
2444
2444
2445 @property
2445 @property
2446 def repositories(self):
2446 def repositories(self):
2447 return Repository.query()\
2447 return Repository.query()\
2448 .filter(Repository.group == self)\
2448 .filter(Repository.group == self)\
2449 .order_by(Repository.repo_name)
2449 .order_by(Repository.repo_name)
2450
2450
2451 @property
2451 @property
2452 def repositories_recursive_count(self):
2452 def repositories_recursive_count(self):
2453 cnt = self.repositories.count()
2453 cnt = self.repositories.count()
2454
2454
2455 def children_count(group):
2455 def children_count(group):
2456 cnt = 0
2456 cnt = 0
2457 for child in group.children:
2457 for child in group.children:
2458 cnt += child.repositories.count()
2458 cnt += child.repositories.count()
2459 cnt += children_count(child)
2459 cnt += children_count(child)
2460 return cnt
2460 return cnt
2461
2461
2462 return cnt + children_count(self)
2462 return cnt + children_count(self)
2463
2463
2464 def _recursive_objects(self, include_repos=True):
2464 def _recursive_objects(self, include_repos=True):
2465 all_ = []
2465 all_ = []
2466
2466
2467 def _get_members(root_gr):
2467 def _get_members(root_gr):
2468 if include_repos:
2468 if include_repos:
2469 for r in root_gr.repositories:
2469 for r in root_gr.repositories:
2470 all_.append(r)
2470 all_.append(r)
2471 childs = root_gr.children.all()
2471 childs = root_gr.children.all()
2472 if childs:
2472 if childs:
2473 for gr in childs:
2473 for gr in childs:
2474 all_.append(gr)
2474 all_.append(gr)
2475 _get_members(gr)
2475 _get_members(gr)
2476
2476
2477 _get_members(self)
2477 _get_members(self)
2478 return [self] + all_
2478 return [self] + all_
2479
2479
2480 def recursive_groups_and_repos(self):
2480 def recursive_groups_and_repos(self):
2481 """
2481 """
2482 Recursive return all groups, with repositories in those groups
2482 Recursive return all groups, with repositories in those groups
2483 """
2483 """
2484 return self._recursive_objects()
2484 return self._recursive_objects()
2485
2485
2486 def recursive_groups(self):
2486 def recursive_groups(self):
2487 """
2487 """
2488 Returns all children groups for this group including children of children
2488 Returns all children groups for this group including children of children
2489 """
2489 """
2490 return self._recursive_objects(include_repos=False)
2490 return self._recursive_objects(include_repos=False)
2491
2491
2492 def get_new_name(self, group_name):
2492 def get_new_name(self, group_name):
2493 """
2493 """
2494 returns new full group name based on parent and new name
2494 returns new full group name based on parent and new name
2495
2495
2496 :param group_name:
2496 :param group_name:
2497 """
2497 """
2498 path_prefix = (self.parent_group.full_path_splitted if
2498 path_prefix = (self.parent_group.full_path_splitted if
2499 self.parent_group else [])
2499 self.parent_group else [])
2500 return RepoGroup.url_sep().join(path_prefix + [group_name])
2500 return RepoGroup.url_sep().join(path_prefix + [group_name])
2501
2501
2502 def permissions(self, with_admins=True, with_owner=True):
2502 def permissions(self, with_admins=True, with_owner=True):
2503 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2503 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2504 q = q.options(joinedload(UserRepoGroupToPerm.group),
2504 q = q.options(joinedload(UserRepoGroupToPerm.group),
2505 joinedload(UserRepoGroupToPerm.user),
2505 joinedload(UserRepoGroupToPerm.user),
2506 joinedload(UserRepoGroupToPerm.permission),)
2506 joinedload(UserRepoGroupToPerm.permission),)
2507
2507
2508 # get owners and admins and permissions. We do a trick of re-writing
2508 # get owners and admins and permissions. We do a trick of re-writing
2509 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2509 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2510 # has a global reference and changing one object propagates to all
2510 # has a global reference and changing one object propagates to all
2511 # others. This means if admin is also an owner admin_row that change
2511 # others. This means if admin is also an owner admin_row that change
2512 # would propagate to both objects
2512 # would propagate to both objects
2513 perm_rows = []
2513 perm_rows = []
2514 for _usr in q.all():
2514 for _usr in q.all():
2515 usr = AttributeDict(_usr.user.get_dict())
2515 usr = AttributeDict(_usr.user.get_dict())
2516 usr.permission = _usr.permission.permission_name
2516 usr.permission = _usr.permission.permission_name
2517 perm_rows.append(usr)
2517 perm_rows.append(usr)
2518
2518
2519 # filter the perm rows by 'default' first and then sort them by
2519 # filter the perm rows by 'default' first and then sort them by
2520 # admin,write,read,none permissions sorted again alphabetically in
2520 # admin,write,read,none permissions sorted again alphabetically in
2521 # each group
2521 # each group
2522 perm_rows = sorted(perm_rows, key=display_user_sort)
2522 perm_rows = sorted(perm_rows, key=display_user_sort)
2523
2523
2524 _admin_perm = 'group.admin'
2524 _admin_perm = 'group.admin'
2525 owner_row = []
2525 owner_row = []
2526 if with_owner:
2526 if with_owner:
2527 usr = AttributeDict(self.user.get_dict())
2527 usr = AttributeDict(self.user.get_dict())
2528 usr.owner_row = True
2528 usr.owner_row = True
2529 usr.permission = _admin_perm
2529 usr.permission = _admin_perm
2530 owner_row.append(usr)
2530 owner_row.append(usr)
2531
2531
2532 super_admin_rows = []
2532 super_admin_rows = []
2533 if with_admins:
2533 if with_admins:
2534 for usr in User.get_all_super_admins():
2534 for usr in User.get_all_super_admins():
2535 # if this admin is also owner, don't double the record
2535 # if this admin is also owner, don't double the record
2536 if usr.user_id == owner_row[0].user_id:
2536 if usr.user_id == owner_row[0].user_id:
2537 owner_row[0].admin_row = True
2537 owner_row[0].admin_row = True
2538 else:
2538 else:
2539 usr = AttributeDict(usr.get_dict())
2539 usr = AttributeDict(usr.get_dict())
2540 usr.admin_row = True
2540 usr.admin_row = True
2541 usr.permission = _admin_perm
2541 usr.permission = _admin_perm
2542 super_admin_rows.append(usr)
2542 super_admin_rows.append(usr)
2543
2543
2544 return super_admin_rows + owner_row + perm_rows
2544 return super_admin_rows + owner_row + perm_rows
2545
2545
2546 def permission_user_groups(self):
2546 def permission_user_groups(self):
2547 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2547 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2548 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2548 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2549 joinedload(UserGroupRepoGroupToPerm.users_group),
2549 joinedload(UserGroupRepoGroupToPerm.users_group),
2550 joinedload(UserGroupRepoGroupToPerm.permission),)
2550 joinedload(UserGroupRepoGroupToPerm.permission),)
2551
2551
2552 perm_rows = []
2552 perm_rows = []
2553 for _user_group in q.all():
2553 for _user_group in q.all():
2554 usr = AttributeDict(_user_group.users_group.get_dict())
2554 usr = AttributeDict(_user_group.users_group.get_dict())
2555 usr.permission = _user_group.permission.permission_name
2555 usr.permission = _user_group.permission.permission_name
2556 perm_rows.append(usr)
2556 perm_rows.append(usr)
2557
2557
2558 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2558 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2559 return perm_rows
2559 return perm_rows
2560
2560
2561 def get_api_data(self):
2561 def get_api_data(self):
2562 """
2562 """
2563 Common function for generating api data
2563 Common function for generating api data
2564
2564
2565 """
2565 """
2566 group = self
2566 group = self
2567 data = {
2567 data = {
2568 'group_id': group.group_id,
2568 'group_id': group.group_id,
2569 'group_name': group.group_name,
2569 'group_name': group.group_name,
2570 'group_description': group.description_safe,
2570 'group_description': group.description_safe,
2571 'parent_group': group.parent_group.group_name if group.parent_group else None,
2571 'parent_group': group.parent_group.group_name if group.parent_group else None,
2572 'repositories': [x.repo_name for x in group.repositories],
2572 'repositories': [x.repo_name for x in group.repositories],
2573 'owner': group.user.username,
2573 'owner': group.user.username,
2574 }
2574 }
2575 return data
2575 return data
2576
2576
2577
2577
2578 class Permission(Base, BaseModel):
2578 class Permission(Base, BaseModel):
2579 __tablename__ = 'permissions'
2579 __tablename__ = 'permissions'
2580 __table_args__ = (
2580 __table_args__ = (
2581 Index('p_perm_name_idx', 'permission_name'),
2581 Index('p_perm_name_idx', 'permission_name'),
2582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2583 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2583 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2584 )
2584 )
2585 PERMS = [
2585 PERMS = [
2586 ('hg.admin', _('RhodeCode Super Administrator')),
2586 ('hg.admin', _('RhodeCode Super Administrator')),
2587
2587
2588 ('repository.none', _('Repository no access')),
2588 ('repository.none', _('Repository no access')),
2589 ('repository.read', _('Repository read access')),
2589 ('repository.read', _('Repository read access')),
2590 ('repository.write', _('Repository write access')),
2590 ('repository.write', _('Repository write access')),
2591 ('repository.admin', _('Repository admin access')),
2591 ('repository.admin', _('Repository admin access')),
2592
2592
2593 ('group.none', _('Repository group no access')),
2593 ('group.none', _('Repository group no access')),
2594 ('group.read', _('Repository group read access')),
2594 ('group.read', _('Repository group read access')),
2595 ('group.write', _('Repository group write access')),
2595 ('group.write', _('Repository group write access')),
2596 ('group.admin', _('Repository group admin access')),
2596 ('group.admin', _('Repository group admin access')),
2597
2597
2598 ('usergroup.none', _('User group no access')),
2598 ('usergroup.none', _('User group no access')),
2599 ('usergroup.read', _('User group read access')),
2599 ('usergroup.read', _('User group read access')),
2600 ('usergroup.write', _('User group write access')),
2600 ('usergroup.write', _('User group write access')),
2601 ('usergroup.admin', _('User group admin access')),
2601 ('usergroup.admin', _('User group admin access')),
2602
2602
2603 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2603 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2604 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2604 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2605
2605
2606 ('hg.usergroup.create.false', _('User Group creation disabled')),
2606 ('hg.usergroup.create.false', _('User Group creation disabled')),
2607 ('hg.usergroup.create.true', _('User Group creation enabled')),
2607 ('hg.usergroup.create.true', _('User Group creation enabled')),
2608
2608
2609 ('hg.create.none', _('Repository creation disabled')),
2609 ('hg.create.none', _('Repository creation disabled')),
2610 ('hg.create.repository', _('Repository creation enabled')),
2610 ('hg.create.repository', _('Repository creation enabled')),
2611 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2611 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2612 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2612 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2613
2613
2614 ('hg.fork.none', _('Repository forking disabled')),
2614 ('hg.fork.none', _('Repository forking disabled')),
2615 ('hg.fork.repository', _('Repository forking enabled')),
2615 ('hg.fork.repository', _('Repository forking enabled')),
2616
2616
2617 ('hg.register.none', _('Registration disabled')),
2617 ('hg.register.none', _('Registration disabled')),
2618 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2618 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2619 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2619 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2620
2620
2621 ('hg.password_reset.enabled', _('Password reset enabled')),
2621 ('hg.password_reset.enabled', _('Password reset enabled')),
2622 ('hg.password_reset.hidden', _('Password reset hidden')),
2622 ('hg.password_reset.hidden', _('Password reset hidden')),
2623 ('hg.password_reset.disabled', _('Password reset disabled')),
2623 ('hg.password_reset.disabled', _('Password reset disabled')),
2624
2624
2625 ('hg.extern_activate.manual', _('Manual activation of external account')),
2625 ('hg.extern_activate.manual', _('Manual activation of external account')),
2626 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2626 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2627
2627
2628 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2628 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2629 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2629 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2630 ]
2630 ]
2631
2631
2632 # definition of system default permissions for DEFAULT user
2632 # definition of system default permissions for DEFAULT user
2633 DEFAULT_USER_PERMISSIONS = [
2633 DEFAULT_USER_PERMISSIONS = [
2634 'repository.read',
2634 'repository.read',
2635 'group.read',
2635 'group.read',
2636 'usergroup.read',
2636 'usergroup.read',
2637 'hg.create.repository',
2637 'hg.create.repository',
2638 'hg.repogroup.create.false',
2638 'hg.repogroup.create.false',
2639 'hg.usergroup.create.false',
2639 'hg.usergroup.create.false',
2640 'hg.create.write_on_repogroup.true',
2640 'hg.create.write_on_repogroup.true',
2641 'hg.fork.repository',
2641 'hg.fork.repository',
2642 'hg.register.manual_activate',
2642 'hg.register.manual_activate',
2643 'hg.password_reset.enabled',
2643 'hg.password_reset.enabled',
2644 'hg.extern_activate.auto',
2644 'hg.extern_activate.auto',
2645 'hg.inherit_default_perms.true',
2645 'hg.inherit_default_perms.true',
2646 ]
2646 ]
2647
2647
2648 # defines which permissions are more important higher the more important
2648 # defines which permissions are more important higher the more important
2649 # Weight defines which permissions are more important.
2649 # Weight defines which permissions are more important.
2650 # The higher number the more important.
2650 # The higher number the more important.
2651 PERM_WEIGHTS = {
2651 PERM_WEIGHTS = {
2652 'repository.none': 0,
2652 'repository.none': 0,
2653 'repository.read': 1,
2653 'repository.read': 1,
2654 'repository.write': 3,
2654 'repository.write': 3,
2655 'repository.admin': 4,
2655 'repository.admin': 4,
2656
2656
2657 'group.none': 0,
2657 'group.none': 0,
2658 'group.read': 1,
2658 'group.read': 1,
2659 'group.write': 3,
2659 'group.write': 3,
2660 'group.admin': 4,
2660 'group.admin': 4,
2661
2661
2662 'usergroup.none': 0,
2662 'usergroup.none': 0,
2663 'usergroup.read': 1,
2663 'usergroup.read': 1,
2664 'usergroup.write': 3,
2664 'usergroup.write': 3,
2665 'usergroup.admin': 4,
2665 'usergroup.admin': 4,
2666
2666
2667 'hg.repogroup.create.false': 0,
2667 'hg.repogroup.create.false': 0,
2668 'hg.repogroup.create.true': 1,
2668 'hg.repogroup.create.true': 1,
2669
2669
2670 'hg.usergroup.create.false': 0,
2670 'hg.usergroup.create.false': 0,
2671 'hg.usergroup.create.true': 1,
2671 'hg.usergroup.create.true': 1,
2672
2672
2673 'hg.fork.none': 0,
2673 'hg.fork.none': 0,
2674 'hg.fork.repository': 1,
2674 'hg.fork.repository': 1,
2675 'hg.create.none': 0,
2675 'hg.create.none': 0,
2676 'hg.create.repository': 1
2676 'hg.create.repository': 1
2677 }
2677 }
2678
2678
2679 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2679 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2680 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2680 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2681 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2681 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2682
2682
2683 def __unicode__(self):
2683 def __unicode__(self):
2684 return u"<%s('%s:%s')>" % (
2684 return u"<%s('%s:%s')>" % (
2685 self.__class__.__name__, self.permission_id, self.permission_name
2685 self.__class__.__name__, self.permission_id, self.permission_name
2686 )
2686 )
2687
2687
2688 @classmethod
2688 @classmethod
2689 def get_by_key(cls, key):
2689 def get_by_key(cls, key):
2690 return cls.query().filter(cls.permission_name == key).scalar()
2690 return cls.query().filter(cls.permission_name == key).scalar()
2691
2691
2692 @classmethod
2692 @classmethod
2693 def get_default_repo_perms(cls, user_id, repo_id=None):
2693 def get_default_repo_perms(cls, user_id, repo_id=None):
2694 q = Session().query(UserRepoToPerm, Repository, Permission)\
2694 q = Session().query(UserRepoToPerm, Repository, Permission)\
2695 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2695 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2696 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2696 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2697 .filter(UserRepoToPerm.user_id == user_id)
2697 .filter(UserRepoToPerm.user_id == user_id)
2698 if repo_id:
2698 if repo_id:
2699 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2699 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2700 return q.all()
2700 return q.all()
2701
2701
2702 @classmethod
2702 @classmethod
2703 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2703 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2704 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2704 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2705 .join(
2705 .join(
2706 Permission,
2706 Permission,
2707 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2707 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2708 .join(
2708 .join(
2709 Repository,
2709 Repository,
2710 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2710 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2711 .join(
2711 .join(
2712 UserGroup,
2712 UserGroup,
2713 UserGroupRepoToPerm.users_group_id ==
2713 UserGroupRepoToPerm.users_group_id ==
2714 UserGroup.users_group_id)\
2714 UserGroup.users_group_id)\
2715 .join(
2715 .join(
2716 UserGroupMember,
2716 UserGroupMember,
2717 UserGroupRepoToPerm.users_group_id ==
2717 UserGroupRepoToPerm.users_group_id ==
2718 UserGroupMember.users_group_id)\
2718 UserGroupMember.users_group_id)\
2719 .filter(
2719 .filter(
2720 UserGroupMember.user_id == user_id,
2720 UserGroupMember.user_id == user_id,
2721 UserGroup.users_group_active == true())
2721 UserGroup.users_group_active == true())
2722 if repo_id:
2722 if repo_id:
2723 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2723 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2724 return q.all()
2724 return q.all()
2725
2725
2726 @classmethod
2726 @classmethod
2727 def get_default_group_perms(cls, user_id, repo_group_id=None):
2727 def get_default_group_perms(cls, user_id, repo_group_id=None):
2728 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2728 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2729 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2729 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2730 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2730 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2731 .filter(UserRepoGroupToPerm.user_id == user_id)
2731 .filter(UserRepoGroupToPerm.user_id == user_id)
2732 if repo_group_id:
2732 if repo_group_id:
2733 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2733 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2734 return q.all()
2734 return q.all()
2735
2735
2736 @classmethod
2736 @classmethod
2737 def get_default_group_perms_from_user_group(
2737 def get_default_group_perms_from_user_group(
2738 cls, user_id, repo_group_id=None):
2738 cls, user_id, repo_group_id=None):
2739 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2739 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2740 .join(
2740 .join(
2741 Permission,
2741 Permission,
2742 UserGroupRepoGroupToPerm.permission_id ==
2742 UserGroupRepoGroupToPerm.permission_id ==
2743 Permission.permission_id)\
2743 Permission.permission_id)\
2744 .join(
2744 .join(
2745 RepoGroup,
2745 RepoGroup,
2746 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2746 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2747 .join(
2747 .join(
2748 UserGroup,
2748 UserGroup,
2749 UserGroupRepoGroupToPerm.users_group_id ==
2749 UserGroupRepoGroupToPerm.users_group_id ==
2750 UserGroup.users_group_id)\
2750 UserGroup.users_group_id)\
2751 .join(
2751 .join(
2752 UserGroupMember,
2752 UserGroupMember,
2753 UserGroupRepoGroupToPerm.users_group_id ==
2753 UserGroupRepoGroupToPerm.users_group_id ==
2754 UserGroupMember.users_group_id)\
2754 UserGroupMember.users_group_id)\
2755 .filter(
2755 .filter(
2756 UserGroupMember.user_id == user_id,
2756 UserGroupMember.user_id == user_id,
2757 UserGroup.users_group_active == true())
2757 UserGroup.users_group_active == true())
2758 if repo_group_id:
2758 if repo_group_id:
2759 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2759 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2760 return q.all()
2760 return q.all()
2761
2761
2762 @classmethod
2762 @classmethod
2763 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2763 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2764 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2764 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2765 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2765 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2766 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2766 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2767 .filter(UserUserGroupToPerm.user_id == user_id)
2767 .filter(UserUserGroupToPerm.user_id == user_id)
2768 if user_group_id:
2768 if user_group_id:
2769 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2769 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2770 return q.all()
2770 return q.all()
2771
2771
2772 @classmethod
2772 @classmethod
2773 def get_default_user_group_perms_from_user_group(
2773 def get_default_user_group_perms_from_user_group(
2774 cls, user_id, user_group_id=None):
2774 cls, user_id, user_group_id=None):
2775 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2775 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2776 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2776 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2777 .join(
2777 .join(
2778 Permission,
2778 Permission,
2779 UserGroupUserGroupToPerm.permission_id ==
2779 UserGroupUserGroupToPerm.permission_id ==
2780 Permission.permission_id)\
2780 Permission.permission_id)\
2781 .join(
2781 .join(
2782 TargetUserGroup,
2782 TargetUserGroup,
2783 UserGroupUserGroupToPerm.target_user_group_id ==
2783 UserGroupUserGroupToPerm.target_user_group_id ==
2784 TargetUserGroup.users_group_id)\
2784 TargetUserGroup.users_group_id)\
2785 .join(
2785 .join(
2786 UserGroup,
2786 UserGroup,
2787 UserGroupUserGroupToPerm.user_group_id ==
2787 UserGroupUserGroupToPerm.user_group_id ==
2788 UserGroup.users_group_id)\
2788 UserGroup.users_group_id)\
2789 .join(
2789 .join(
2790 UserGroupMember,
2790 UserGroupMember,
2791 UserGroupUserGroupToPerm.user_group_id ==
2791 UserGroupUserGroupToPerm.user_group_id ==
2792 UserGroupMember.users_group_id)\
2792 UserGroupMember.users_group_id)\
2793 .filter(
2793 .filter(
2794 UserGroupMember.user_id == user_id,
2794 UserGroupMember.user_id == user_id,
2795 UserGroup.users_group_active == true())
2795 UserGroup.users_group_active == true())
2796 if user_group_id:
2796 if user_group_id:
2797 q = q.filter(
2797 q = q.filter(
2798 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2798 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2799
2799
2800 return q.all()
2800 return q.all()
2801
2801
2802
2802
2803 class UserRepoToPerm(Base, BaseModel):
2803 class UserRepoToPerm(Base, BaseModel):
2804 __tablename__ = 'repo_to_perm'
2804 __tablename__ = 'repo_to_perm'
2805 __table_args__ = (
2805 __table_args__ = (
2806 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2806 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2807 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2807 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2808 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2808 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2809 )
2809 )
2810 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2810 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2811 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2811 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2812 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2812 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2813 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2813 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2814
2814
2815 user = relationship('User')
2815 user = relationship('User')
2816 repository = relationship('Repository')
2816 repository = relationship('Repository')
2817 permission = relationship('Permission')
2817 permission = relationship('Permission')
2818
2818
2819 @classmethod
2819 @classmethod
2820 def create(cls, user, repository, permission):
2820 def create(cls, user, repository, permission):
2821 n = cls()
2821 n = cls()
2822 n.user = user
2822 n.user = user
2823 n.repository = repository
2823 n.repository = repository
2824 n.permission = permission
2824 n.permission = permission
2825 Session().add(n)
2825 Session().add(n)
2826 return n
2826 return n
2827
2827
2828 def __unicode__(self):
2828 def __unicode__(self):
2829 return u'<%s => %s >' % (self.user, self.repository)
2829 return u'<%s => %s >' % (self.user, self.repository)
2830
2830
2831
2831
2832 class UserUserGroupToPerm(Base, BaseModel):
2832 class UserUserGroupToPerm(Base, BaseModel):
2833 __tablename__ = 'user_user_group_to_perm'
2833 __tablename__ = 'user_user_group_to_perm'
2834 __table_args__ = (
2834 __table_args__ = (
2835 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2835 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2836 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2836 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2837 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2837 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2838 )
2838 )
2839 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2839 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2840 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2840 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2841 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2841 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2842 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2842 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2843
2843
2844 user = relationship('User')
2844 user = relationship('User')
2845 user_group = relationship('UserGroup')
2845 user_group = relationship('UserGroup')
2846 permission = relationship('Permission')
2846 permission = relationship('Permission')
2847
2847
2848 @classmethod
2848 @classmethod
2849 def create(cls, user, user_group, permission):
2849 def create(cls, user, user_group, permission):
2850 n = cls()
2850 n = cls()
2851 n.user = user
2851 n.user = user
2852 n.user_group = user_group
2852 n.user_group = user_group
2853 n.permission = permission
2853 n.permission = permission
2854 Session().add(n)
2854 Session().add(n)
2855 return n
2855 return n
2856
2856
2857 def __unicode__(self):
2857 def __unicode__(self):
2858 return u'<%s => %s >' % (self.user, self.user_group)
2858 return u'<%s => %s >' % (self.user, self.user_group)
2859
2859
2860
2860
2861 class UserToPerm(Base, BaseModel):
2861 class UserToPerm(Base, BaseModel):
2862 __tablename__ = 'user_to_perm'
2862 __tablename__ = 'user_to_perm'
2863 __table_args__ = (
2863 __table_args__ = (
2864 UniqueConstraint('user_id', 'permission_id'),
2864 UniqueConstraint('user_id', 'permission_id'),
2865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2866 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2866 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2867 )
2867 )
2868 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2868 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2870 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2870 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2871
2871
2872 user = relationship('User')
2872 user = relationship('User')
2873 permission = relationship('Permission', lazy='joined')
2873 permission = relationship('Permission', lazy='joined')
2874
2874
2875 def __unicode__(self):
2875 def __unicode__(self):
2876 return u'<%s => %s >' % (self.user, self.permission)
2876 return u'<%s => %s >' % (self.user, self.permission)
2877
2877
2878
2878
2879 class UserGroupRepoToPerm(Base, BaseModel):
2879 class UserGroupRepoToPerm(Base, BaseModel):
2880 __tablename__ = 'users_group_repo_to_perm'
2880 __tablename__ = 'users_group_repo_to_perm'
2881 __table_args__ = (
2881 __table_args__ = (
2882 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2882 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2885 )
2885 )
2886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2889 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2889 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2890
2890
2891 users_group = relationship('UserGroup')
2891 users_group = relationship('UserGroup')
2892 permission = relationship('Permission')
2892 permission = relationship('Permission')
2893 repository = relationship('Repository')
2893 repository = relationship('Repository')
2894
2894
2895 @classmethod
2895 @classmethod
2896 def create(cls, users_group, repository, permission):
2896 def create(cls, users_group, repository, permission):
2897 n = cls()
2897 n = cls()
2898 n.users_group = users_group
2898 n.users_group = users_group
2899 n.repository = repository
2899 n.repository = repository
2900 n.permission = permission
2900 n.permission = permission
2901 Session().add(n)
2901 Session().add(n)
2902 return n
2902 return n
2903
2903
2904 def __unicode__(self):
2904 def __unicode__(self):
2905 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2905 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2906
2906
2907
2907
2908 class UserGroupUserGroupToPerm(Base, BaseModel):
2908 class UserGroupUserGroupToPerm(Base, BaseModel):
2909 __tablename__ = 'user_group_user_group_to_perm'
2909 __tablename__ = 'user_group_user_group_to_perm'
2910 __table_args__ = (
2910 __table_args__ = (
2911 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2911 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2912 CheckConstraint('target_user_group_id != user_group_id'),
2912 CheckConstraint('target_user_group_id != user_group_id'),
2913 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2913 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2914 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2914 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2915 )
2915 )
2916 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)
2916 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)
2917 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2917 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2919 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2919 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2920
2920
2921 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2921 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2922 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2922 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2923 permission = relationship('Permission')
2923 permission = relationship('Permission')
2924
2924
2925 @classmethod
2925 @classmethod
2926 def create(cls, target_user_group, user_group, permission):
2926 def create(cls, target_user_group, user_group, permission):
2927 n = cls()
2927 n = cls()
2928 n.target_user_group = target_user_group
2928 n.target_user_group = target_user_group
2929 n.user_group = user_group
2929 n.user_group = user_group
2930 n.permission = permission
2930 n.permission = permission
2931 Session().add(n)
2931 Session().add(n)
2932 return n
2932 return n
2933
2933
2934 def __unicode__(self):
2934 def __unicode__(self):
2935 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2935 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2936
2936
2937
2937
2938 class UserGroupToPerm(Base, BaseModel):
2938 class UserGroupToPerm(Base, BaseModel):
2939 __tablename__ = 'users_group_to_perm'
2939 __tablename__ = 'users_group_to_perm'
2940 __table_args__ = (
2940 __table_args__ = (
2941 UniqueConstraint('users_group_id', 'permission_id',),
2941 UniqueConstraint('users_group_id', 'permission_id',),
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2944 )
2944 )
2945 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2945 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2946 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2946 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2947 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2947 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2948
2948
2949 users_group = relationship('UserGroup')
2949 users_group = relationship('UserGroup')
2950 permission = relationship('Permission')
2950 permission = relationship('Permission')
2951
2951
2952
2952
2953 class UserRepoGroupToPerm(Base, BaseModel):
2953 class UserRepoGroupToPerm(Base, BaseModel):
2954 __tablename__ = 'user_repo_group_to_perm'
2954 __tablename__ = 'user_repo_group_to_perm'
2955 __table_args__ = (
2955 __table_args__ = (
2956 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2956 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2959 )
2959 )
2960
2960
2961 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2961 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2963 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2963 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2964 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2964 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2965
2965
2966 user = relationship('User')
2966 user = relationship('User')
2967 group = relationship('RepoGroup')
2967 group = relationship('RepoGroup')
2968 permission = relationship('Permission')
2968 permission = relationship('Permission')
2969
2969
2970 @classmethod
2970 @classmethod
2971 def create(cls, user, repository_group, permission):
2971 def create(cls, user, repository_group, permission):
2972 n = cls()
2972 n = cls()
2973 n.user = user
2973 n.user = user
2974 n.group = repository_group
2974 n.group = repository_group
2975 n.permission = permission
2975 n.permission = permission
2976 Session().add(n)
2976 Session().add(n)
2977 return n
2977 return n
2978
2978
2979
2979
2980 class UserGroupRepoGroupToPerm(Base, BaseModel):
2980 class UserGroupRepoGroupToPerm(Base, BaseModel):
2981 __tablename__ = 'users_group_repo_group_to_perm'
2981 __tablename__ = 'users_group_repo_group_to_perm'
2982 __table_args__ = (
2982 __table_args__ = (
2983 UniqueConstraint('users_group_id', 'group_id'),
2983 UniqueConstraint('users_group_id', 'group_id'),
2984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2986 )
2986 )
2987
2987
2988 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)
2988 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)
2989 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2989 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2990 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2990 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2991 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2991 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2992
2992
2993 users_group = relationship('UserGroup')
2993 users_group = relationship('UserGroup')
2994 permission = relationship('Permission')
2994 permission = relationship('Permission')
2995 group = relationship('RepoGroup')
2995 group = relationship('RepoGroup')
2996
2996
2997 @classmethod
2997 @classmethod
2998 def create(cls, user_group, repository_group, permission):
2998 def create(cls, user_group, repository_group, permission):
2999 n = cls()
2999 n = cls()
3000 n.users_group = user_group
3000 n.users_group = user_group
3001 n.group = repository_group
3001 n.group = repository_group
3002 n.permission = permission
3002 n.permission = permission
3003 Session().add(n)
3003 Session().add(n)
3004 return n
3004 return n
3005
3005
3006 def __unicode__(self):
3006 def __unicode__(self):
3007 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3007 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3008
3008
3009
3009
3010 class Statistics(Base, BaseModel):
3010 class Statistics(Base, BaseModel):
3011 __tablename__ = 'statistics'
3011 __tablename__ = 'statistics'
3012 __table_args__ = (
3012 __table_args__ = (
3013 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3013 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3014 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3014 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3015 )
3015 )
3016 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3016 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3017 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3017 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3018 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3018 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3019 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3019 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3020 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3020 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3021 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3021 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3022
3022
3023 repository = relationship('Repository', single_parent=True)
3023 repository = relationship('Repository', single_parent=True)
3024
3024
3025
3025
3026 class UserFollowing(Base, BaseModel):
3026 class UserFollowing(Base, BaseModel):
3027 __tablename__ = 'user_followings'
3027 __tablename__ = 'user_followings'
3028 __table_args__ = (
3028 __table_args__ = (
3029 UniqueConstraint('user_id', 'follows_repository_id'),
3029 UniqueConstraint('user_id', 'follows_repository_id'),
3030 UniqueConstraint('user_id', 'follows_user_id'),
3030 UniqueConstraint('user_id', 'follows_user_id'),
3031 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3031 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3032 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3032 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3033 )
3033 )
3034
3034
3035 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3035 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3036 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3036 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3037 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3037 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3038 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3038 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3039 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3039 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3040
3040
3041 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3041 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3042
3042
3043 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3043 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3044 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3044 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3045
3045
3046 @classmethod
3046 @classmethod
3047 def get_repo_followers(cls, repo_id):
3047 def get_repo_followers(cls, repo_id):
3048 return cls.query().filter(cls.follows_repo_id == repo_id)
3048 return cls.query().filter(cls.follows_repo_id == repo_id)
3049
3049
3050
3050
3051 class CacheKey(Base, BaseModel):
3051 class CacheKey(Base, BaseModel):
3052 __tablename__ = 'cache_invalidation'
3052 __tablename__ = 'cache_invalidation'
3053 __table_args__ = (
3053 __table_args__ = (
3054 UniqueConstraint('cache_key'),
3054 UniqueConstraint('cache_key'),
3055 Index('key_idx', 'cache_key'),
3055 Index('key_idx', 'cache_key'),
3056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3057 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3057 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3058 )
3058 )
3059 CACHE_TYPE_ATOM = 'ATOM'
3059 CACHE_TYPE_ATOM = 'ATOM'
3060 CACHE_TYPE_RSS = 'RSS'
3060 CACHE_TYPE_RSS = 'RSS'
3061 CACHE_TYPE_README = 'README'
3061 CACHE_TYPE_README = 'README'
3062
3062
3063 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3063 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3064 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3064 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3065 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3065 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3066 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3066 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3067
3067
3068 def __init__(self, cache_key, cache_args=''):
3068 def __init__(self, cache_key, cache_args=''):
3069 self.cache_key = cache_key
3069 self.cache_key = cache_key
3070 self.cache_args = cache_args
3070 self.cache_args = cache_args
3071 self.cache_active = False
3071 self.cache_active = False
3072
3072
3073 def __unicode__(self):
3073 def __unicode__(self):
3074 return u"<%s('%s:%s[%s]')>" % (
3074 return u"<%s('%s:%s[%s]')>" % (
3075 self.__class__.__name__,
3075 self.__class__.__name__,
3076 self.cache_id, self.cache_key, self.cache_active)
3076 self.cache_id, self.cache_key, self.cache_active)
3077
3077
3078 def _cache_key_partition(self):
3078 def _cache_key_partition(self):
3079 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3079 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3080 return prefix, repo_name, suffix
3080 return prefix, repo_name, suffix
3081
3081
3082 def get_prefix(self):
3082 def get_prefix(self):
3083 """
3083 """
3084 Try to extract prefix from existing cache key. The key could consist
3084 Try to extract prefix from existing cache key. The key could consist
3085 of prefix, repo_name, suffix
3085 of prefix, repo_name, suffix
3086 """
3086 """
3087 # this returns prefix, repo_name, suffix
3087 # this returns prefix, repo_name, suffix
3088 return self._cache_key_partition()[0]
3088 return self._cache_key_partition()[0]
3089
3089
3090 def get_suffix(self):
3090 def get_suffix(self):
3091 """
3091 """
3092 get suffix that might have been used in _get_cache_key to
3092 get suffix that might have been used in _get_cache_key to
3093 generate self.cache_key. Only used for informational purposes
3093 generate self.cache_key. Only used for informational purposes
3094 in repo_edit.mako.
3094 in repo_edit.mako.
3095 """
3095 """
3096 # prefix, repo_name, suffix
3096 # prefix, repo_name, suffix
3097 return self._cache_key_partition()[2]
3097 return self._cache_key_partition()[2]
3098
3098
3099 @classmethod
3099 @classmethod
3100 def delete_all_cache(cls):
3100 def delete_all_cache(cls):
3101 """
3101 """
3102 Delete all cache keys from database.
3102 Delete all cache keys from database.
3103 Should only be run when all instances are down and all entries
3103 Should only be run when all instances are down and all entries
3104 thus stale.
3104 thus stale.
3105 """
3105 """
3106 cls.query().delete()
3106 cls.query().delete()
3107 Session().commit()
3107 Session().commit()
3108
3108
3109 @classmethod
3109 @classmethod
3110 def get_cache_key(cls, repo_name, cache_type):
3110 def get_cache_key(cls, repo_name, cache_type):
3111 """
3111 """
3112
3112
3113 Generate a cache key for this process of RhodeCode instance.
3113 Generate a cache key for this process of RhodeCode instance.
3114 Prefix most likely will be process id or maybe explicitly set
3114 Prefix most likely will be process id or maybe explicitly set
3115 instance_id from .ini file.
3115 instance_id from .ini file.
3116 """
3116 """
3117 import rhodecode
3117 import rhodecode
3118 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3118 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3119
3119
3120 repo_as_unicode = safe_unicode(repo_name)
3120 repo_as_unicode = safe_unicode(repo_name)
3121 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3121 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3122 if cache_type else repo_as_unicode
3122 if cache_type else repo_as_unicode
3123
3123
3124 return u'{}{}'.format(prefix, key)
3124 return u'{}{}'.format(prefix, key)
3125
3125
3126 @classmethod
3126 @classmethod
3127 def set_invalidate(cls, repo_name, delete=False):
3127 def set_invalidate(cls, repo_name, delete=False):
3128 """
3128 """
3129 Mark all caches of a repo as invalid in the database.
3129 Mark all caches of a repo as invalid in the database.
3130 """
3130 """
3131
3131
3132 try:
3132 try:
3133 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3133 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3134 if delete:
3134 if delete:
3135 log.debug('cache objects deleted for repo %s',
3135 log.debug('cache objects deleted for repo %s',
3136 safe_str(repo_name))
3136 safe_str(repo_name))
3137 qry.delete()
3137 qry.delete()
3138 else:
3138 else:
3139 log.debug('cache objects marked as invalid for repo %s',
3139 log.debug('cache objects marked as invalid for repo %s',
3140 safe_str(repo_name))
3140 safe_str(repo_name))
3141 qry.update({"cache_active": False})
3141 qry.update({"cache_active": False})
3142
3142
3143 Session().commit()
3143 Session().commit()
3144 except Exception:
3144 except Exception:
3145 log.exception(
3145 log.exception(
3146 'Cache key invalidation failed for repository %s',
3146 'Cache key invalidation failed for repository %s',
3147 safe_str(repo_name))
3147 safe_str(repo_name))
3148 Session().rollback()
3148 Session().rollback()
3149
3149
3150 @classmethod
3150 @classmethod
3151 def get_active_cache(cls, cache_key):
3151 def get_active_cache(cls, cache_key):
3152 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3152 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3153 if inv_obj:
3153 if inv_obj:
3154 return inv_obj
3154 return inv_obj
3155 return None
3155 return None
3156
3156
3157
3157
3158 class ChangesetComment(Base, BaseModel):
3158 class ChangesetComment(Base, BaseModel):
3159 __tablename__ = 'changeset_comments'
3159 __tablename__ = 'changeset_comments'
3160 __table_args__ = (
3160 __table_args__ = (
3161 Index('cc_revision_idx', 'revision'),
3161 Index('cc_revision_idx', 'revision'),
3162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3164 )
3164 )
3165
3165
3166 COMMENT_OUTDATED = u'comment_outdated'
3166 COMMENT_OUTDATED = u'comment_outdated'
3167 COMMENT_TYPE_NOTE = u'note'
3167 COMMENT_TYPE_NOTE = u'note'
3168 COMMENT_TYPE_TODO = u'todo'
3168 COMMENT_TYPE_TODO = u'todo'
3169 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3169 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3170
3170
3171 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3171 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3172 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3172 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3173 revision = Column('revision', String(40), nullable=True)
3173 revision = Column('revision', String(40), nullable=True)
3174 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3174 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3175 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3175 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3176 line_no = Column('line_no', Unicode(10), nullable=True)
3176 line_no = Column('line_no', Unicode(10), nullable=True)
3177 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3177 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3178 f_path = Column('f_path', Unicode(1000), nullable=True)
3178 f_path = Column('f_path', Unicode(1000), nullable=True)
3179 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3179 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3180 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3180 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3181 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3181 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3182 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3182 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3183 renderer = Column('renderer', Unicode(64), nullable=True)
3183 renderer = Column('renderer', Unicode(64), nullable=True)
3184 display_state = Column('display_state', Unicode(128), nullable=True)
3184 display_state = Column('display_state', Unicode(128), nullable=True)
3185
3185
3186 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3186 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3187 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3187 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3188 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3188 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3189 author = relationship('User', lazy='joined')
3189 author = relationship('User', lazy='joined')
3190 repo = relationship('Repository')
3190 repo = relationship('Repository')
3191 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3191 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3192 pull_request = relationship('PullRequest', lazy='joined')
3192 pull_request = relationship('PullRequest', lazy='joined')
3193 pull_request_version = relationship('PullRequestVersion')
3193 pull_request_version = relationship('PullRequestVersion')
3194
3194
3195 @classmethod
3195 @classmethod
3196 def get_users(cls, revision=None, pull_request_id=None):
3196 def get_users(cls, revision=None, pull_request_id=None):
3197 """
3197 """
3198 Returns user associated with this ChangesetComment. ie those
3198 Returns user associated with this ChangesetComment. ie those
3199 who actually commented
3199 who actually commented
3200
3200
3201 :param cls:
3201 :param cls:
3202 :param revision:
3202 :param revision:
3203 """
3203 """
3204 q = Session().query(User)\
3204 q = Session().query(User)\
3205 .join(ChangesetComment.author)
3205 .join(ChangesetComment.author)
3206 if revision:
3206 if revision:
3207 q = q.filter(cls.revision == revision)
3207 q = q.filter(cls.revision == revision)
3208 elif pull_request_id:
3208 elif pull_request_id:
3209 q = q.filter(cls.pull_request_id == pull_request_id)
3209 q = q.filter(cls.pull_request_id == pull_request_id)
3210 return q.all()
3210 return q.all()
3211
3211
3212 @classmethod
3212 @classmethod
3213 def get_index_from_version(cls, pr_version, versions):
3213 def get_index_from_version(cls, pr_version, versions):
3214 num_versions = [x.pull_request_version_id for x in versions]
3214 num_versions = [x.pull_request_version_id for x in versions]
3215 try:
3215 try:
3216 return num_versions.index(pr_version) +1
3216 return num_versions.index(pr_version) +1
3217 except (IndexError, ValueError):
3217 except (IndexError, ValueError):
3218 return
3218 return
3219
3219
3220 @property
3220 @property
3221 def outdated(self):
3221 def outdated(self):
3222 return self.display_state == self.COMMENT_OUTDATED
3222 return self.display_state == self.COMMENT_OUTDATED
3223
3223
3224 def outdated_at_version(self, version):
3224 def outdated_at_version(self, version):
3225 """
3225 """
3226 Checks if comment is outdated for given pull request version
3226 Checks if comment is outdated for given pull request version
3227 """
3227 """
3228 return self.outdated and self.pull_request_version_id != version
3228 return self.outdated and self.pull_request_version_id != version
3229
3229
3230 def older_than_version(self, version):
3230 def older_than_version(self, version):
3231 """
3231 """
3232 Checks if comment is made from previous version than given
3232 Checks if comment is made from previous version than given
3233 """
3233 """
3234 if version is None:
3234 if version is None:
3235 return self.pull_request_version_id is not None
3235 return self.pull_request_version_id is not None
3236
3236
3237 return self.pull_request_version_id < version
3237 return self.pull_request_version_id < version
3238
3238
3239 @property
3239 @property
3240 def resolved(self):
3240 def resolved(self):
3241 return self.resolved_by[0] if self.resolved_by else None
3241 return self.resolved_by[0] if self.resolved_by else None
3242
3242
3243 @property
3243 @property
3244 def is_todo(self):
3244 def is_todo(self):
3245 return self.comment_type == self.COMMENT_TYPE_TODO
3245 return self.comment_type == self.COMMENT_TYPE_TODO
3246
3246
3247 @property
3247 @property
3248 def is_inline(self):
3248 def is_inline(self):
3249 return self.line_no and self.f_path
3249 return self.line_no and self.f_path
3250
3250
3251 def get_index_version(self, versions):
3251 def get_index_version(self, versions):
3252 return self.get_index_from_version(
3252 return self.get_index_from_version(
3253 self.pull_request_version_id, versions)
3253 self.pull_request_version_id, versions)
3254
3254
3255 def __repr__(self):
3255 def __repr__(self):
3256 if self.comment_id:
3256 if self.comment_id:
3257 return '<DB:Comment #%s>' % self.comment_id
3257 return '<DB:Comment #%s>' % self.comment_id
3258 else:
3258 else:
3259 return '<DB:Comment at %#x>' % id(self)
3259 return '<DB:Comment at %#x>' % id(self)
3260
3260
3261 def get_api_data(self):
3261 def get_api_data(self):
3262 comment = self
3262 comment = self
3263 data = {
3263 data = {
3264 'comment_id': comment.comment_id,
3264 'comment_id': comment.comment_id,
3265 'comment_type': comment.comment_type,
3265 'comment_type': comment.comment_type,
3266 'comment_text': comment.text,
3266 'comment_text': comment.text,
3267 'comment_status': comment.status_change,
3267 'comment_status': comment.status_change,
3268 'comment_f_path': comment.f_path,
3268 'comment_f_path': comment.f_path,
3269 'comment_lineno': comment.line_no,
3269 'comment_lineno': comment.line_no,
3270 'comment_author': comment.author,
3270 'comment_author': comment.author,
3271 'comment_created_on': comment.created_on
3271 'comment_created_on': comment.created_on
3272 }
3272 }
3273 return data
3273 return data
3274
3274
3275 def __json__(self):
3275 def __json__(self):
3276 data = dict()
3276 data = dict()
3277 data.update(self.get_api_data())
3277 data.update(self.get_api_data())
3278 return data
3278 return data
3279
3279
3280
3280
3281 class ChangesetStatus(Base, BaseModel):
3281 class ChangesetStatus(Base, BaseModel):
3282 __tablename__ = 'changeset_statuses'
3282 __tablename__ = 'changeset_statuses'
3283 __table_args__ = (
3283 __table_args__ = (
3284 Index('cs_revision_idx', 'revision'),
3284 Index('cs_revision_idx', 'revision'),
3285 Index('cs_version_idx', 'version'),
3285 Index('cs_version_idx', 'version'),
3286 UniqueConstraint('repo_id', 'revision', 'version'),
3286 UniqueConstraint('repo_id', 'revision', 'version'),
3287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3289 )
3289 )
3290 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3290 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3291 STATUS_APPROVED = 'approved'
3291 STATUS_APPROVED = 'approved'
3292 STATUS_REJECTED = 'rejected'
3292 STATUS_REJECTED = 'rejected'
3293 STATUS_UNDER_REVIEW = 'under_review'
3293 STATUS_UNDER_REVIEW = 'under_review'
3294
3294
3295 STATUSES = [
3295 STATUSES = [
3296 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3296 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3297 (STATUS_APPROVED, _("Approved")),
3297 (STATUS_APPROVED, _("Approved")),
3298 (STATUS_REJECTED, _("Rejected")),
3298 (STATUS_REJECTED, _("Rejected")),
3299 (STATUS_UNDER_REVIEW, _("Under Review")),
3299 (STATUS_UNDER_REVIEW, _("Under Review")),
3300 ]
3300 ]
3301
3301
3302 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3302 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3303 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3303 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3305 revision = Column('revision', String(40), nullable=False)
3305 revision = Column('revision', String(40), nullable=False)
3306 status = Column('status', String(128), nullable=False, default=DEFAULT)
3306 status = Column('status', String(128), nullable=False, default=DEFAULT)
3307 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3307 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3308 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3308 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3309 version = Column('version', Integer(), nullable=False, default=0)
3309 version = Column('version', Integer(), nullable=False, default=0)
3310 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3310 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3311
3311
3312 author = relationship('User', lazy='joined')
3312 author = relationship('User', lazy='joined')
3313 repo = relationship('Repository')
3313 repo = relationship('Repository')
3314 comment = relationship('ChangesetComment', lazy='joined')
3314 comment = relationship('ChangesetComment', lazy='joined')
3315 pull_request = relationship('PullRequest', lazy='joined')
3315 pull_request = relationship('PullRequest', lazy='joined')
3316
3316
3317 def __unicode__(self):
3317 def __unicode__(self):
3318 return u"<%s('%s[v%s]:%s')>" % (
3318 return u"<%s('%s[v%s]:%s')>" % (
3319 self.__class__.__name__,
3319 self.__class__.__name__,
3320 self.status, self.version, self.author
3320 self.status, self.version, self.author
3321 )
3321 )
3322
3322
3323 @classmethod
3323 @classmethod
3324 def get_status_lbl(cls, value):
3324 def get_status_lbl(cls, value):
3325 return dict(cls.STATUSES).get(value)
3325 return dict(cls.STATUSES).get(value)
3326
3326
3327 @property
3327 @property
3328 def status_lbl(self):
3328 def status_lbl(self):
3329 return ChangesetStatus.get_status_lbl(self.status)
3329 return ChangesetStatus.get_status_lbl(self.status)
3330
3330
3331 def get_api_data(self):
3331 def get_api_data(self):
3332 status = self
3332 status = self
3333 data = {
3333 data = {
3334 'status_id': status.changeset_status_id,
3334 'status_id': status.changeset_status_id,
3335 'status': status.status,
3335 'status': status.status,
3336 }
3336 }
3337 return data
3337 return data
3338
3338
3339 def __json__(self):
3339 def __json__(self):
3340 data = dict()
3340 data = dict()
3341 data.update(self.get_api_data())
3341 data.update(self.get_api_data())
3342 return data
3342 return data
3343
3343
3344
3344
3345 class _PullRequestBase(BaseModel):
3345 class _PullRequestBase(BaseModel):
3346 """
3346 """
3347 Common attributes of pull request and version entries.
3347 Common attributes of pull request and version entries.
3348 """
3348 """
3349
3349
3350 # .status values
3350 # .status values
3351 STATUS_NEW = u'new'
3351 STATUS_NEW = u'new'
3352 STATUS_OPEN = u'open'
3352 STATUS_OPEN = u'open'
3353 STATUS_CLOSED = u'closed'
3353 STATUS_CLOSED = u'closed'
3354
3354
3355 title = Column('title', Unicode(255), nullable=True)
3355 title = Column('title', Unicode(255), nullable=True)
3356 description = Column(
3356 description = Column(
3357 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3357 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3358 nullable=True)
3358 nullable=True)
3359 # new/open/closed status of pull request (not approve/reject/etc)
3359 # new/open/closed status of pull request (not approve/reject/etc)
3360 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3360 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3361 created_on = Column(
3361 created_on = Column(
3362 'created_on', DateTime(timezone=False), nullable=False,
3362 'created_on', DateTime(timezone=False), nullable=False,
3363 default=datetime.datetime.now)
3363 default=datetime.datetime.now)
3364 updated_on = Column(
3364 updated_on = Column(
3365 'updated_on', DateTime(timezone=False), nullable=False,
3365 'updated_on', DateTime(timezone=False), nullable=False,
3366 default=datetime.datetime.now)
3366 default=datetime.datetime.now)
3367
3367
3368 @declared_attr
3368 @declared_attr
3369 def user_id(cls):
3369 def user_id(cls):
3370 return Column(
3370 return Column(
3371 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3371 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3372 unique=None)
3372 unique=None)
3373
3373
3374 # 500 revisions max
3374 # 500 revisions max
3375 _revisions = Column(
3375 _revisions = Column(
3376 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3376 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3377
3377
3378 @declared_attr
3378 @declared_attr
3379 def source_repo_id(cls):
3379 def source_repo_id(cls):
3380 # TODO: dan: rename column to source_repo_id
3380 # TODO: dan: rename column to source_repo_id
3381 return Column(
3381 return Column(
3382 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3382 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3383 nullable=False)
3383 nullable=False)
3384
3384
3385 source_ref = Column('org_ref', Unicode(255), nullable=False)
3385 source_ref = Column('org_ref', Unicode(255), nullable=False)
3386
3386
3387 @declared_attr
3387 @declared_attr
3388 def target_repo_id(cls):
3388 def target_repo_id(cls):
3389 # TODO: dan: rename column to target_repo_id
3389 # TODO: dan: rename column to target_repo_id
3390 return Column(
3390 return Column(
3391 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3391 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3392 nullable=False)
3392 nullable=False)
3393
3393
3394 target_ref = Column('other_ref', Unicode(255), nullable=False)
3394 target_ref = Column('other_ref', Unicode(255), nullable=False)
3395 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3395 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3396
3396
3397 # TODO: dan: rename column to last_merge_source_rev
3397 # TODO: dan: rename column to last_merge_source_rev
3398 _last_merge_source_rev = Column(
3398 _last_merge_source_rev = Column(
3399 'last_merge_org_rev', String(40), nullable=True)
3399 'last_merge_org_rev', String(40), nullable=True)
3400 # TODO: dan: rename column to last_merge_target_rev
3400 # TODO: dan: rename column to last_merge_target_rev
3401 _last_merge_target_rev = Column(
3401 _last_merge_target_rev = Column(
3402 'last_merge_other_rev', String(40), nullable=True)
3402 'last_merge_other_rev', String(40), nullable=True)
3403 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3403 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3404 merge_rev = Column('merge_rev', String(40), nullable=True)
3404 merge_rev = Column('merge_rev', String(40), nullable=True)
3405
3405
3406 reviewer_data = Column(
3406 reviewer_data = Column(
3407 'reviewer_data_json', MutationObj.as_mutable(
3407 'reviewer_data_json', MutationObj.as_mutable(
3408 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3408 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3409
3409
3410 @property
3410 @property
3411 def reviewer_data_json(self):
3411 def reviewer_data_json(self):
3412 return json.dumps(self.reviewer_data)
3412 return json.dumps(self.reviewer_data)
3413
3413
3414 @hybrid_property
3414 @hybrid_property
3415 def description_safe(self):
3415 def description_safe(self):
3416 from rhodecode.lib import helpers as h
3416 from rhodecode.lib import helpers as h
3417 return h.escape(self.description)
3417 return h.escape(self.description)
3418
3418
3419 @hybrid_property
3419 @hybrid_property
3420 def revisions(self):
3420 def revisions(self):
3421 return self._revisions.split(':') if self._revisions else []
3421 return self._revisions.split(':') if self._revisions else []
3422
3422
3423 @revisions.setter
3423 @revisions.setter
3424 def revisions(self, val):
3424 def revisions(self, val):
3425 self._revisions = ':'.join(val)
3425 self._revisions = ':'.join(val)
3426
3426
3427 @hybrid_property
3427 @hybrid_property
3428 def last_merge_status(self):
3428 def last_merge_status(self):
3429 return safe_int(self._last_merge_status)
3429 return safe_int(self._last_merge_status)
3430
3430
3431 @last_merge_status.setter
3431 @last_merge_status.setter
3432 def last_merge_status(self, val):
3432 def last_merge_status(self, val):
3433 self._last_merge_status = val
3433 self._last_merge_status = val
3434
3434
3435 @declared_attr
3435 @declared_attr
3436 def author(cls):
3436 def author(cls):
3437 return relationship('User', lazy='joined')
3437 return relationship('User', lazy='joined')
3438
3438
3439 @declared_attr
3439 @declared_attr
3440 def source_repo(cls):
3440 def source_repo(cls):
3441 return relationship(
3441 return relationship(
3442 'Repository',
3442 'Repository',
3443 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3443 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3444
3444
3445 @property
3445 @property
3446 def source_ref_parts(self):
3446 def source_ref_parts(self):
3447 return self.unicode_to_reference(self.source_ref)
3447 return self.unicode_to_reference(self.source_ref)
3448
3448
3449 @declared_attr
3449 @declared_attr
3450 def target_repo(cls):
3450 def target_repo(cls):
3451 return relationship(
3451 return relationship(
3452 'Repository',
3452 'Repository',
3453 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3453 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3454
3454
3455 @property
3455 @property
3456 def target_ref_parts(self):
3456 def target_ref_parts(self):
3457 return self.unicode_to_reference(self.target_ref)
3457 return self.unicode_to_reference(self.target_ref)
3458
3458
3459 @property
3459 @property
3460 def shadow_merge_ref(self):
3460 def shadow_merge_ref(self):
3461 return self.unicode_to_reference(self._shadow_merge_ref)
3461 return self.unicode_to_reference(self._shadow_merge_ref)
3462
3462
3463 @shadow_merge_ref.setter
3463 @shadow_merge_ref.setter
3464 def shadow_merge_ref(self, ref):
3464 def shadow_merge_ref(self, ref):
3465 self._shadow_merge_ref = self.reference_to_unicode(ref)
3465 self._shadow_merge_ref = self.reference_to_unicode(ref)
3466
3466
3467 def unicode_to_reference(self, raw):
3467 def unicode_to_reference(self, raw):
3468 """
3468 """
3469 Convert a unicode (or string) to a reference object.
3469 Convert a unicode (or string) to a reference object.
3470 If unicode evaluates to False it returns None.
3470 If unicode evaluates to False it returns None.
3471 """
3471 """
3472 if raw:
3472 if raw:
3473 refs = raw.split(':')
3473 refs = raw.split(':')
3474 return Reference(*refs)
3474 return Reference(*refs)
3475 else:
3475 else:
3476 return None
3476 return None
3477
3477
3478 def reference_to_unicode(self, ref):
3478 def reference_to_unicode(self, ref):
3479 """
3479 """
3480 Convert a reference object to unicode.
3480 Convert a reference object to unicode.
3481 If reference is None it returns None.
3481 If reference is None it returns None.
3482 """
3482 """
3483 if ref:
3483 if ref:
3484 return u':'.join(ref)
3484 return u':'.join(ref)
3485 else:
3485 else:
3486 return None
3486 return None
3487
3487
3488 def get_api_data(self, with_merge_state=True):
3488 def get_api_data(self, with_merge_state=True):
3489 from rhodecode.model.pull_request import PullRequestModel
3489 from rhodecode.model.pull_request import PullRequestModel
3490
3490
3491 pull_request = self
3491 pull_request = self
3492 if with_merge_state:
3492 if with_merge_state:
3493 merge_status = PullRequestModel().merge_status(pull_request)
3493 merge_status = PullRequestModel().merge_status(pull_request)
3494 merge_state = {
3494 merge_state = {
3495 'status': merge_status[0],
3495 'status': merge_status[0],
3496 'message': safe_unicode(merge_status[1]),
3496 'message': safe_unicode(merge_status[1]),
3497 }
3497 }
3498 else:
3498 else:
3499 merge_state = {'status': 'not_available',
3499 merge_state = {'status': 'not_available',
3500 'message': 'not_available'}
3500 'message': 'not_available'}
3501
3501
3502 merge_data = {
3502 merge_data = {
3503 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3503 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3504 'reference': (
3504 'reference': (
3505 pull_request.shadow_merge_ref._asdict()
3505 pull_request.shadow_merge_ref._asdict()
3506 if pull_request.shadow_merge_ref else None),
3506 if pull_request.shadow_merge_ref else None),
3507 }
3507 }
3508
3508
3509 data = {
3509 data = {
3510 'pull_request_id': pull_request.pull_request_id,
3510 'pull_request_id': pull_request.pull_request_id,
3511 'url': PullRequestModel().get_url(pull_request),
3511 'url': PullRequestModel().get_url(pull_request),
3512 'title': pull_request.title,
3512 'title': pull_request.title,
3513 'description': pull_request.description,
3513 'description': pull_request.description,
3514 'status': pull_request.status,
3514 'status': pull_request.status,
3515 'created_on': pull_request.created_on,
3515 'created_on': pull_request.created_on,
3516 'updated_on': pull_request.updated_on,
3516 'updated_on': pull_request.updated_on,
3517 'commit_ids': pull_request.revisions,
3517 'commit_ids': pull_request.revisions,
3518 'review_status': pull_request.calculated_review_status(),
3518 'review_status': pull_request.calculated_review_status(),
3519 'mergeable': merge_state,
3519 'mergeable': merge_state,
3520 'source': {
3520 'source': {
3521 'clone_url': pull_request.source_repo.clone_url(),
3521 'clone_url': pull_request.source_repo.clone_url(),
3522 'repository': pull_request.source_repo.repo_name,
3522 'repository': pull_request.source_repo.repo_name,
3523 'reference': {
3523 'reference': {
3524 'name': pull_request.source_ref_parts.name,
3524 'name': pull_request.source_ref_parts.name,
3525 'type': pull_request.source_ref_parts.type,
3525 'type': pull_request.source_ref_parts.type,
3526 'commit_id': pull_request.source_ref_parts.commit_id,
3526 'commit_id': pull_request.source_ref_parts.commit_id,
3527 },
3527 },
3528 },
3528 },
3529 'target': {
3529 'target': {
3530 'clone_url': pull_request.target_repo.clone_url(),
3530 'clone_url': pull_request.target_repo.clone_url(),
3531 'repository': pull_request.target_repo.repo_name,
3531 'repository': pull_request.target_repo.repo_name,
3532 'reference': {
3532 'reference': {
3533 'name': pull_request.target_ref_parts.name,
3533 'name': pull_request.target_ref_parts.name,
3534 'type': pull_request.target_ref_parts.type,
3534 'type': pull_request.target_ref_parts.type,
3535 'commit_id': pull_request.target_ref_parts.commit_id,
3535 'commit_id': pull_request.target_ref_parts.commit_id,
3536 },
3536 },
3537 },
3537 },
3538 'merge': merge_data,
3538 'merge': merge_data,
3539 'author': pull_request.author.get_api_data(include_secrets=False,
3539 'author': pull_request.author.get_api_data(include_secrets=False,
3540 details='basic'),
3540 details='basic'),
3541 'reviewers': [
3541 'reviewers': [
3542 {
3542 {
3543 'user': reviewer.get_api_data(include_secrets=False,
3543 'user': reviewer.get_api_data(include_secrets=False,
3544 details='basic'),
3544 details='basic'),
3545 'reasons': reasons,
3545 'reasons': reasons,
3546 'review_status': st[0][1].status if st else 'not_reviewed',
3546 'review_status': st[0][1].status if st else 'not_reviewed',
3547 }
3547 }
3548 for reviewer, reasons, mandatory, st in
3548 for reviewer, reasons, mandatory, st in
3549 pull_request.reviewers_statuses()
3549 pull_request.reviewers_statuses()
3550 ]
3550 ]
3551 }
3551 }
3552
3552
3553 return data
3553 return data
3554
3554
3555
3555
3556 class PullRequest(Base, _PullRequestBase):
3556 class PullRequest(Base, _PullRequestBase):
3557 __tablename__ = 'pull_requests'
3557 __tablename__ = 'pull_requests'
3558 __table_args__ = (
3558 __table_args__ = (
3559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3561 )
3561 )
3562
3562
3563 pull_request_id = Column(
3563 pull_request_id = Column(
3564 'pull_request_id', Integer(), nullable=False, primary_key=True)
3564 'pull_request_id', Integer(), nullable=False, primary_key=True)
3565
3565
3566 def __repr__(self):
3566 def __repr__(self):
3567 if self.pull_request_id:
3567 if self.pull_request_id:
3568 return '<DB:PullRequest #%s>' % self.pull_request_id
3568 return '<DB:PullRequest #%s>' % self.pull_request_id
3569 else:
3569 else:
3570 return '<DB:PullRequest at %#x>' % id(self)
3570 return '<DB:PullRequest at %#x>' % id(self)
3571
3571
3572 reviewers = relationship('PullRequestReviewers',
3572 reviewers = relationship('PullRequestReviewers',
3573 cascade="all, delete, delete-orphan")
3573 cascade="all, delete, delete-orphan")
3574 statuses = relationship('ChangesetStatus',
3574 statuses = relationship('ChangesetStatus',
3575 cascade="all, delete, delete-orphan")
3575 cascade="all, delete, delete-orphan")
3576 comments = relationship('ChangesetComment',
3576 comments = relationship('ChangesetComment',
3577 cascade="all, delete, delete-orphan")
3577 cascade="all, delete, delete-orphan")
3578 versions = relationship('PullRequestVersion',
3578 versions = relationship('PullRequestVersion',
3579 cascade="all, delete, delete-orphan",
3579 cascade="all, delete, delete-orphan",
3580 lazy='dynamic')
3580 lazy='dynamic')
3581
3581
3582 @classmethod
3582 @classmethod
3583 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3583 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3584 internal_methods=None):
3584 internal_methods=None):
3585
3585
3586 class PullRequestDisplay(object):
3586 class PullRequestDisplay(object):
3587 """
3587 """
3588 Special object wrapper for showing PullRequest data via Versions
3588 Special object wrapper for showing PullRequest data via Versions
3589 It mimics PR object as close as possible. This is read only object
3589 It mimics PR object as close as possible. This is read only object
3590 just for display
3590 just for display
3591 """
3591 """
3592
3592
3593 def __init__(self, attrs, internal=None):
3593 def __init__(self, attrs, internal=None):
3594 self.attrs = attrs
3594 self.attrs = attrs
3595 # internal have priority over the given ones via attrs
3595 # internal have priority over the given ones via attrs
3596 self.internal = internal or ['versions']
3596 self.internal = internal or ['versions']
3597
3597
3598 def __getattr__(self, item):
3598 def __getattr__(self, item):
3599 if item in self.internal:
3599 if item in self.internal:
3600 return getattr(self, item)
3600 return getattr(self, item)
3601 try:
3601 try:
3602 return self.attrs[item]
3602 return self.attrs[item]
3603 except KeyError:
3603 except KeyError:
3604 raise AttributeError(
3604 raise AttributeError(
3605 '%s object has no attribute %s' % (self, item))
3605 '%s object has no attribute %s' % (self, item))
3606
3606
3607 def __repr__(self):
3607 def __repr__(self):
3608 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3608 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3609
3609
3610 def versions(self):
3610 def versions(self):
3611 return pull_request_obj.versions.order_by(
3611 return pull_request_obj.versions.order_by(
3612 PullRequestVersion.pull_request_version_id).all()
3612 PullRequestVersion.pull_request_version_id).all()
3613
3613
3614 def is_closed(self):
3614 def is_closed(self):
3615 return pull_request_obj.is_closed()
3615 return pull_request_obj.is_closed()
3616
3616
3617 @property
3617 @property
3618 def pull_request_version_id(self):
3618 def pull_request_version_id(self):
3619 return getattr(pull_request_obj, 'pull_request_version_id', None)
3619 return getattr(pull_request_obj, 'pull_request_version_id', None)
3620
3620
3621 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3621 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3622
3622
3623 attrs.author = StrictAttributeDict(
3623 attrs.author = StrictAttributeDict(
3624 pull_request_obj.author.get_api_data())
3624 pull_request_obj.author.get_api_data())
3625 if pull_request_obj.target_repo:
3625 if pull_request_obj.target_repo:
3626 attrs.target_repo = StrictAttributeDict(
3626 attrs.target_repo = StrictAttributeDict(
3627 pull_request_obj.target_repo.get_api_data())
3627 pull_request_obj.target_repo.get_api_data())
3628 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3628 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3629
3629
3630 if pull_request_obj.source_repo:
3630 if pull_request_obj.source_repo:
3631 attrs.source_repo = StrictAttributeDict(
3631 attrs.source_repo = StrictAttributeDict(
3632 pull_request_obj.source_repo.get_api_data())
3632 pull_request_obj.source_repo.get_api_data())
3633 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3633 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3634
3634
3635 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3635 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3636 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3636 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3637 attrs.revisions = pull_request_obj.revisions
3637 attrs.revisions = pull_request_obj.revisions
3638
3638
3639 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3639 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3640 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3640 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3641 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3641 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3642
3642
3643 return PullRequestDisplay(attrs, internal=internal_methods)
3643 return PullRequestDisplay(attrs, internal=internal_methods)
3644
3644
3645 def is_closed(self):
3645 def is_closed(self):
3646 return self.status == self.STATUS_CLOSED
3646 return self.status == self.STATUS_CLOSED
3647
3647
3648 def __json__(self):
3648 def __json__(self):
3649 return {
3649 return {
3650 'revisions': self.revisions,
3650 'revisions': self.revisions,
3651 }
3651 }
3652
3652
3653 def calculated_review_status(self):
3653 def calculated_review_status(self):
3654 from rhodecode.model.changeset_status import ChangesetStatusModel
3654 from rhodecode.model.changeset_status import ChangesetStatusModel
3655 return ChangesetStatusModel().calculated_review_status(self)
3655 return ChangesetStatusModel().calculated_review_status(self)
3656
3656
3657 def reviewers_statuses(self):
3657 def reviewers_statuses(self):
3658 from rhodecode.model.changeset_status import ChangesetStatusModel
3658 from rhodecode.model.changeset_status import ChangesetStatusModel
3659 return ChangesetStatusModel().reviewers_statuses(self)
3659 return ChangesetStatusModel().reviewers_statuses(self)
3660
3660
3661 @property
3661 @property
3662 def workspace_id(self):
3662 def workspace_id(self):
3663 from rhodecode.model.pull_request import PullRequestModel
3663 from rhodecode.model.pull_request import PullRequestModel
3664 return PullRequestModel()._workspace_id(self)
3664 return PullRequestModel()._workspace_id(self)
3665
3665
3666 def get_shadow_repo(self):
3666 def get_shadow_repo(self):
3667 workspace_id = self.workspace_id
3667 workspace_id = self.workspace_id
3668 vcs_obj = self.target_repo.scm_instance()
3668 vcs_obj = self.target_repo.scm_instance()
3669 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3669 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3670 workspace_id)
3670 workspace_id)
3671 return vcs_obj._get_shadow_instance(shadow_repository_path)
3671 return vcs_obj._get_shadow_instance(shadow_repository_path)
3672
3672
3673
3673
3674 class PullRequestVersion(Base, _PullRequestBase):
3674 class PullRequestVersion(Base, _PullRequestBase):
3675 __tablename__ = 'pull_request_versions'
3675 __tablename__ = 'pull_request_versions'
3676 __table_args__ = (
3676 __table_args__ = (
3677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3679 )
3679 )
3680
3680
3681 pull_request_version_id = Column(
3681 pull_request_version_id = Column(
3682 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3682 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3683 pull_request_id = Column(
3683 pull_request_id = Column(
3684 'pull_request_id', Integer(),
3684 'pull_request_id', Integer(),
3685 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3685 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3686 pull_request = relationship('PullRequest')
3686 pull_request = relationship('PullRequest')
3687
3687
3688 def __repr__(self):
3688 def __repr__(self):
3689 if self.pull_request_version_id:
3689 if self.pull_request_version_id:
3690 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3690 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3691 else:
3691 else:
3692 return '<DB:PullRequestVersion at %#x>' % id(self)
3692 return '<DB:PullRequestVersion at %#x>' % id(self)
3693
3693
3694 @property
3694 @property
3695 def reviewers(self):
3695 def reviewers(self):
3696 return self.pull_request.reviewers
3696 return self.pull_request.reviewers
3697
3697
3698 @property
3698 @property
3699 def versions(self):
3699 def versions(self):
3700 return self.pull_request.versions
3700 return self.pull_request.versions
3701
3701
3702 def is_closed(self):
3702 def is_closed(self):
3703 # calculate from original
3703 # calculate from original
3704 return self.pull_request.status == self.STATUS_CLOSED
3704 return self.pull_request.status == self.STATUS_CLOSED
3705
3705
3706 def calculated_review_status(self):
3706 def calculated_review_status(self):
3707 return self.pull_request.calculated_review_status()
3707 return self.pull_request.calculated_review_status()
3708
3708
3709 def reviewers_statuses(self):
3709 def reviewers_statuses(self):
3710 return self.pull_request.reviewers_statuses()
3710 return self.pull_request.reviewers_statuses()
3711
3711
3712
3712
3713 class PullRequestReviewers(Base, BaseModel):
3713 class PullRequestReviewers(Base, BaseModel):
3714 __tablename__ = 'pull_request_reviewers'
3714 __tablename__ = 'pull_request_reviewers'
3715 __table_args__ = (
3715 __table_args__ = (
3716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3718 )
3718 )
3719
3719
3720 @hybrid_property
3720 @hybrid_property
3721 def reasons(self):
3721 def reasons(self):
3722 if not self._reasons:
3722 if not self._reasons:
3723 return []
3723 return []
3724 return self._reasons
3724 return self._reasons
3725
3725
3726 @reasons.setter
3726 @reasons.setter
3727 def reasons(self, val):
3727 def reasons(self, val):
3728 val = val or []
3728 val = val or []
3729 if any(not isinstance(x, basestring) for x in val):
3729 if any(not isinstance(x, basestring) for x in val):
3730 raise Exception('invalid reasons type, must be list of strings')
3730 raise Exception('invalid reasons type, must be list of strings')
3731 self._reasons = val
3731 self._reasons = val
3732
3732
3733 pull_requests_reviewers_id = Column(
3733 pull_requests_reviewers_id = Column(
3734 'pull_requests_reviewers_id', Integer(), nullable=False,
3734 'pull_requests_reviewers_id', Integer(), nullable=False,
3735 primary_key=True)
3735 primary_key=True)
3736 pull_request_id = Column(
3736 pull_request_id = Column(
3737 "pull_request_id", Integer(),
3737 "pull_request_id", Integer(),
3738 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3738 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3739 user_id = Column(
3739 user_id = Column(
3740 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3740 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3741 _reasons = Column(
3741 _reasons = Column(
3742 'reason', MutationList.as_mutable(
3742 'reason', MutationList.as_mutable(
3743 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3743 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3744 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3744 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3745 user = relationship('User')
3745 user = relationship('User')
3746 pull_request = relationship('PullRequest')
3746 pull_request = relationship('PullRequest')
3747
3747
3748
3748
3749 class Notification(Base, BaseModel):
3749 class Notification(Base, BaseModel):
3750 __tablename__ = 'notifications'
3750 __tablename__ = 'notifications'
3751 __table_args__ = (
3751 __table_args__ = (
3752 Index('notification_type_idx', 'type'),
3752 Index('notification_type_idx', 'type'),
3753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3755 )
3755 )
3756
3756
3757 TYPE_CHANGESET_COMMENT = u'cs_comment'
3757 TYPE_CHANGESET_COMMENT = u'cs_comment'
3758 TYPE_MESSAGE = u'message'
3758 TYPE_MESSAGE = u'message'
3759 TYPE_MENTION = u'mention'
3759 TYPE_MENTION = u'mention'
3760 TYPE_REGISTRATION = u'registration'
3760 TYPE_REGISTRATION = u'registration'
3761 TYPE_PULL_REQUEST = u'pull_request'
3761 TYPE_PULL_REQUEST = u'pull_request'
3762 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3762 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3763
3763
3764 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3764 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3765 subject = Column('subject', Unicode(512), nullable=True)
3765 subject = Column('subject', Unicode(512), nullable=True)
3766 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3766 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3767 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3767 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3768 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3768 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3769 type_ = Column('type', Unicode(255))
3769 type_ = Column('type', Unicode(255))
3770
3770
3771 created_by_user = relationship('User')
3771 created_by_user = relationship('User')
3772 notifications_to_users = relationship('UserNotification', lazy='joined',
3772 notifications_to_users = relationship('UserNotification', lazy='joined',
3773 cascade="all, delete, delete-orphan")
3773 cascade="all, delete, delete-orphan")
3774
3774
3775 @property
3775 @property
3776 def recipients(self):
3776 def recipients(self):
3777 return [x.user for x in UserNotification.query()\
3777 return [x.user for x in UserNotification.query()\
3778 .filter(UserNotification.notification == self)\
3778 .filter(UserNotification.notification == self)\
3779 .order_by(UserNotification.user_id.asc()).all()]
3779 .order_by(UserNotification.user_id.asc()).all()]
3780
3780
3781 @classmethod
3781 @classmethod
3782 def create(cls, created_by, subject, body, recipients, type_=None):
3782 def create(cls, created_by, subject, body, recipients, type_=None):
3783 if type_ is None:
3783 if type_ is None:
3784 type_ = Notification.TYPE_MESSAGE
3784 type_ = Notification.TYPE_MESSAGE
3785
3785
3786 notification = cls()
3786 notification = cls()
3787 notification.created_by_user = created_by
3787 notification.created_by_user = created_by
3788 notification.subject = subject
3788 notification.subject = subject
3789 notification.body = body
3789 notification.body = body
3790 notification.type_ = type_
3790 notification.type_ = type_
3791 notification.created_on = datetime.datetime.now()
3791 notification.created_on = datetime.datetime.now()
3792
3792
3793 for u in recipients:
3793 for u in recipients:
3794 assoc = UserNotification()
3794 assoc = UserNotification()
3795 assoc.notification = notification
3795 assoc.notification = notification
3796
3796
3797 # if created_by is inside recipients mark his notification
3797 # if created_by is inside recipients mark his notification
3798 # as read
3798 # as read
3799 if u.user_id == created_by.user_id:
3799 if u.user_id == created_by.user_id:
3800 assoc.read = True
3800 assoc.read = True
3801
3801
3802 u.notifications.append(assoc)
3802 u.notifications.append(assoc)
3803 Session().add(notification)
3803 Session().add(notification)
3804
3804
3805 return notification
3805 return notification
3806
3806
3807
3807
3808 class UserNotification(Base, BaseModel):
3808 class UserNotification(Base, BaseModel):
3809 __tablename__ = 'user_to_notification'
3809 __tablename__ = 'user_to_notification'
3810 __table_args__ = (
3810 __table_args__ = (
3811 UniqueConstraint('user_id', 'notification_id'),
3811 UniqueConstraint('user_id', 'notification_id'),
3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3814 )
3814 )
3815 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3815 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3816 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3816 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3817 read = Column('read', Boolean, default=False)
3817 read = Column('read', Boolean, default=False)
3818 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3818 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3819
3819
3820 user = relationship('User', lazy="joined")
3820 user = relationship('User', lazy="joined")
3821 notification = relationship('Notification', lazy="joined",
3821 notification = relationship('Notification', lazy="joined",
3822 order_by=lambda: Notification.created_on.desc(),)
3822 order_by=lambda: Notification.created_on.desc(),)
3823
3823
3824 def mark_as_read(self):
3824 def mark_as_read(self):
3825 self.read = True
3825 self.read = True
3826 Session().add(self)
3826 Session().add(self)
3827
3827
3828
3828
3829 class Gist(Base, BaseModel):
3829 class Gist(Base, BaseModel):
3830 __tablename__ = 'gists'
3830 __tablename__ = 'gists'
3831 __table_args__ = (
3831 __table_args__ = (
3832 Index('g_gist_access_id_idx', 'gist_access_id'),
3832 Index('g_gist_access_id_idx', 'gist_access_id'),
3833 Index('g_created_on_idx', 'created_on'),
3833 Index('g_created_on_idx', 'created_on'),
3834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3836 )
3836 )
3837 GIST_PUBLIC = u'public'
3837 GIST_PUBLIC = u'public'
3838 GIST_PRIVATE = u'private'
3838 GIST_PRIVATE = u'private'
3839 DEFAULT_FILENAME = u'gistfile1.txt'
3839 DEFAULT_FILENAME = u'gistfile1.txt'
3840
3840
3841 ACL_LEVEL_PUBLIC = u'acl_public'
3841 ACL_LEVEL_PUBLIC = u'acl_public'
3842 ACL_LEVEL_PRIVATE = u'acl_private'
3842 ACL_LEVEL_PRIVATE = u'acl_private'
3843
3843
3844 gist_id = Column('gist_id', Integer(), primary_key=True)
3844 gist_id = Column('gist_id', Integer(), primary_key=True)
3845 gist_access_id = Column('gist_access_id', Unicode(250))
3845 gist_access_id = Column('gist_access_id', Unicode(250))
3846 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3846 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3847 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3847 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3848 gist_expires = Column('gist_expires', Float(53), nullable=False)
3848 gist_expires = Column('gist_expires', Float(53), nullable=False)
3849 gist_type = Column('gist_type', Unicode(128), nullable=False)
3849 gist_type = Column('gist_type', Unicode(128), nullable=False)
3850 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3850 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3851 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3851 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3852 acl_level = Column('acl_level', Unicode(128), nullable=True)
3852 acl_level = Column('acl_level', Unicode(128), nullable=True)
3853
3853
3854 owner = relationship('User')
3854 owner = relationship('User')
3855
3855
3856 def __repr__(self):
3856 def __repr__(self):
3857 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3857 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3858
3858
3859 @hybrid_property
3859 @hybrid_property
3860 def description_safe(self):
3860 def description_safe(self):
3861 from rhodecode.lib import helpers as h
3861 from rhodecode.lib import helpers as h
3862 return h.escape(self.gist_description)
3862 return h.escape(self.gist_description)
3863
3863
3864 @classmethod
3864 @classmethod
3865 def get_or_404(cls, id_):
3865 def get_or_404(cls, id_):
3866 from pyramid.httpexceptions import HTTPNotFound
3866 from pyramid.httpexceptions import HTTPNotFound
3867
3867
3868 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3868 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3869 if not res:
3869 if not res:
3870 raise HTTPNotFound()
3870 raise HTTPNotFound()
3871 return res
3871 return res
3872
3872
3873 @classmethod
3873 @classmethod
3874 def get_by_access_id(cls, gist_access_id):
3874 def get_by_access_id(cls, gist_access_id):
3875 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3875 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3876
3876
3877 def gist_url(self):
3877 def gist_url(self):
3878 from rhodecode.model.gist import GistModel
3878 from rhodecode.model.gist import GistModel
3879 return GistModel().get_url(self)
3879 return GistModel().get_url(self)
3880
3880
3881 @classmethod
3881 @classmethod
3882 def base_path(cls):
3882 def base_path(cls):
3883 """
3883 """
3884 Returns base path when all gists are stored
3884 Returns base path when all gists are stored
3885
3885
3886 :param cls:
3886 :param cls:
3887 """
3887 """
3888 from rhodecode.model.gist import GIST_STORE_LOC
3888 from rhodecode.model.gist import GIST_STORE_LOC
3889 q = Session().query(RhodeCodeUi)\
3889 q = Session().query(RhodeCodeUi)\
3890 .filter(RhodeCodeUi.ui_key == URL_SEP)
3890 .filter(RhodeCodeUi.ui_key == URL_SEP)
3891 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3891 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3892 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3892 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3893
3893
3894 def get_api_data(self):
3894 def get_api_data(self):
3895 """
3895 """
3896 Common function for generating gist related data for API
3896 Common function for generating gist related data for API
3897 """
3897 """
3898 gist = self
3898 gist = self
3899 data = {
3899 data = {
3900 'gist_id': gist.gist_id,
3900 'gist_id': gist.gist_id,
3901 'type': gist.gist_type,
3901 'type': gist.gist_type,
3902 'access_id': gist.gist_access_id,
3902 'access_id': gist.gist_access_id,
3903 'description': gist.gist_description,
3903 'description': gist.gist_description,
3904 'url': gist.gist_url(),
3904 'url': gist.gist_url(),
3905 'expires': gist.gist_expires,
3905 'expires': gist.gist_expires,
3906 'created_on': gist.created_on,
3906 'created_on': gist.created_on,
3907 'modified_at': gist.modified_at,
3907 'modified_at': gist.modified_at,
3908 'content': None,
3908 'content': None,
3909 'acl_level': gist.acl_level,
3909 'acl_level': gist.acl_level,
3910 }
3910 }
3911 return data
3911 return data
3912
3912
3913 def __json__(self):
3913 def __json__(self):
3914 data = dict(
3914 data = dict(
3915 )
3915 )
3916 data.update(self.get_api_data())
3916 data.update(self.get_api_data())
3917 return data
3917 return data
3918 # SCM functions
3918 # SCM functions
3919
3919
3920 def scm_instance(self, **kwargs):
3920 def scm_instance(self, **kwargs):
3921 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3921 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3922 return get_vcs_instance(
3922 return get_vcs_instance(
3923 repo_path=safe_str(full_repo_path), create=False)
3923 repo_path=safe_str(full_repo_path), create=False)
3924
3924
3925
3925
3926 class ExternalIdentity(Base, BaseModel):
3926 class ExternalIdentity(Base, BaseModel):
3927 __tablename__ = 'external_identities'
3927 __tablename__ = 'external_identities'
3928 __table_args__ = (
3928 __table_args__ = (
3929 Index('local_user_id_idx', 'local_user_id'),
3929 Index('local_user_id_idx', 'local_user_id'),
3930 Index('external_id_idx', 'external_id'),
3930 Index('external_id_idx', 'external_id'),
3931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3932 'mysql_charset': 'utf8'})
3932 'mysql_charset': 'utf8'})
3933
3933
3934 external_id = Column('external_id', Unicode(255), default=u'',
3934 external_id = Column('external_id', Unicode(255), default=u'',
3935 primary_key=True)
3935 primary_key=True)
3936 external_username = Column('external_username', Unicode(1024), default=u'')
3936 external_username = Column('external_username', Unicode(1024), default=u'')
3937 local_user_id = Column('local_user_id', Integer(),
3937 local_user_id = Column('local_user_id', Integer(),
3938 ForeignKey('users.user_id'), primary_key=True)
3938 ForeignKey('users.user_id'), primary_key=True)
3939 provider_name = Column('provider_name', Unicode(255), default=u'',
3939 provider_name = Column('provider_name', Unicode(255), default=u'',
3940 primary_key=True)
3940 primary_key=True)
3941 access_token = Column('access_token', String(1024), default=u'')
3941 access_token = Column('access_token', String(1024), default=u'')
3942 alt_token = Column('alt_token', String(1024), default=u'')
3942 alt_token = Column('alt_token', String(1024), default=u'')
3943 token_secret = Column('token_secret', String(1024), default=u'')
3943 token_secret = Column('token_secret', String(1024), default=u'')
3944
3944
3945 @classmethod
3945 @classmethod
3946 def by_external_id_and_provider(cls, external_id, provider_name,
3946 def by_external_id_and_provider(cls, external_id, provider_name,
3947 local_user_id=None):
3947 local_user_id=None):
3948 """
3948 """
3949 Returns ExternalIdentity instance based on search params
3949 Returns ExternalIdentity instance based on search params
3950
3950
3951 :param external_id:
3951 :param external_id:
3952 :param provider_name:
3952 :param provider_name:
3953 :return: ExternalIdentity
3953 :return: ExternalIdentity
3954 """
3954 """
3955 query = cls.query()
3955 query = cls.query()
3956 query = query.filter(cls.external_id == external_id)
3956 query = query.filter(cls.external_id == external_id)
3957 query = query.filter(cls.provider_name == provider_name)
3957 query = query.filter(cls.provider_name == provider_name)
3958 if local_user_id:
3958 if local_user_id:
3959 query = query.filter(cls.local_user_id == local_user_id)
3959 query = query.filter(cls.local_user_id == local_user_id)
3960 return query.first()
3960 return query.first()
3961
3961
3962 @classmethod
3962 @classmethod
3963 def user_by_external_id_and_provider(cls, external_id, provider_name):
3963 def user_by_external_id_and_provider(cls, external_id, provider_name):
3964 """
3964 """
3965 Returns User instance based on search params
3965 Returns User instance based on search params
3966
3966
3967 :param external_id:
3967 :param external_id:
3968 :param provider_name:
3968 :param provider_name:
3969 :return: User
3969 :return: User
3970 """
3970 """
3971 query = User.query()
3971 query = User.query()
3972 query = query.filter(cls.external_id == external_id)
3972 query = query.filter(cls.external_id == external_id)
3973 query = query.filter(cls.provider_name == provider_name)
3973 query = query.filter(cls.provider_name == provider_name)
3974 query = query.filter(User.user_id == cls.local_user_id)
3974 query = query.filter(User.user_id == cls.local_user_id)
3975 return query.first()
3975 return query.first()
3976
3976
3977 @classmethod
3977 @classmethod
3978 def by_local_user_id(cls, local_user_id):
3978 def by_local_user_id(cls, local_user_id):
3979 """
3979 """
3980 Returns all tokens for user
3980 Returns all tokens for user
3981
3981
3982 :param local_user_id:
3982 :param local_user_id:
3983 :return: ExternalIdentity
3983 :return: ExternalIdentity
3984 """
3984 """
3985 query = cls.query()
3985 query = cls.query()
3986 query = query.filter(cls.local_user_id == local_user_id)
3986 query = query.filter(cls.local_user_id == local_user_id)
3987 return query
3987 return query
3988
3988
3989
3989
3990 class Integration(Base, BaseModel):
3990 class Integration(Base, BaseModel):
3991 __tablename__ = 'integrations'
3991 __tablename__ = 'integrations'
3992 __table_args__ = (
3992 __table_args__ = (
3993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3995 )
3995 )
3996
3996
3997 integration_id = Column('integration_id', Integer(), primary_key=True)
3997 integration_id = Column('integration_id', Integer(), primary_key=True)
3998 integration_type = Column('integration_type', String(255))
3998 integration_type = Column('integration_type', String(255))
3999 enabled = Column('enabled', Boolean(), nullable=False)
3999 enabled = Column('enabled', Boolean(), nullable=False)
4000 name = Column('name', String(255), nullable=False)
4000 name = Column('name', String(255), nullable=False)
4001 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4001 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4002 default=False)
4002 default=False)
4003
4003
4004 settings = Column(
4004 settings = Column(
4005 'settings_json', MutationObj.as_mutable(
4005 'settings_json', MutationObj.as_mutable(
4006 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4006 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4007 repo_id = Column(
4007 repo_id = Column(
4008 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4008 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4009 nullable=True, unique=None, default=None)
4009 nullable=True, unique=None, default=None)
4010 repo = relationship('Repository', lazy='joined')
4010 repo = relationship('Repository', lazy='joined')
4011
4011
4012 repo_group_id = Column(
4012 repo_group_id = Column(
4013 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4013 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4014 nullable=True, unique=None, default=None)
4014 nullable=True, unique=None, default=None)
4015 repo_group = relationship('RepoGroup', lazy='joined')
4015 repo_group = relationship('RepoGroup', lazy='joined')
4016
4016
4017 @property
4017 @property
4018 def scope(self):
4018 def scope(self):
4019 if self.repo:
4019 if self.repo:
4020 return repr(self.repo)
4020 return repr(self.repo)
4021 if self.repo_group:
4021 if self.repo_group:
4022 if self.child_repos_only:
4022 if self.child_repos_only:
4023 return repr(self.repo_group) + ' (child repos only)'
4023 return repr(self.repo_group) + ' (child repos only)'
4024 else:
4024 else:
4025 return repr(self.repo_group) + ' (recursive)'
4025 return repr(self.repo_group) + ' (recursive)'
4026 if self.child_repos_only:
4026 if self.child_repos_only:
4027 return 'root_repos'
4027 return 'root_repos'
4028 return 'global'
4028 return 'global'
4029
4029
4030 def __repr__(self):
4030 def __repr__(self):
4031 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4031 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4032
4032
4033
4033
4034 class RepoReviewRuleUser(Base, BaseModel):
4034 class RepoReviewRuleUser(Base, BaseModel):
4035 __tablename__ = 'repo_review_rules_users'
4035 __tablename__ = 'repo_review_rules_users'
4036 __table_args__ = (
4036 __table_args__ = (
4037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4039 )
4039 )
4040 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4040 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4041 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4041 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4043 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4043 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4044 user = relationship('User')
4044 user = relationship('User')
4045
4045
4046 def rule_data(self):
4046 def rule_data(self):
4047 return {
4047 return {
4048 'mandatory': self.mandatory
4048 'mandatory': self.mandatory
4049 }
4049 }
4050
4050
4051
4051
4052 class RepoReviewRuleUserGroup(Base, BaseModel):
4052 class RepoReviewRuleUserGroup(Base, BaseModel):
4053 __tablename__ = 'repo_review_rules_users_groups'
4053 __tablename__ = 'repo_review_rules_users_groups'
4054 __table_args__ = (
4054 __table_args__ = (
4055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4057 )
4057 )
4058 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4058 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4059 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4059 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4060 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4060 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4061 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4061 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4062 users_group = relationship('UserGroup')
4062 users_group = relationship('UserGroup')
4063
4063
4064 def rule_data(self):
4064 def rule_data(self):
4065 return {
4065 return {
4066 'mandatory': self.mandatory
4066 'mandatory': self.mandatory
4067 }
4067 }
4068
4068
4069
4069
4070 class RepoReviewRule(Base, BaseModel):
4070 class RepoReviewRule(Base, BaseModel):
4071 __tablename__ = 'repo_review_rules'
4071 __tablename__ = 'repo_review_rules'
4072 __table_args__ = (
4072 __table_args__ = (
4073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4075 )
4075 )
4076
4076
4077 repo_review_rule_id = Column(
4077 repo_review_rule_id = Column(
4078 'repo_review_rule_id', Integer(), primary_key=True)
4078 'repo_review_rule_id', Integer(), primary_key=True)
4079 repo_id = Column(
4079 repo_id = Column(
4080 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4080 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4081 repo = relationship('Repository', backref='review_rules')
4081 repo = relationship('Repository', backref='review_rules')
4082
4082
4083 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4083 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4084 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4084 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4085
4085
4086 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4086 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4087 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4087 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4088 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4088 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4089 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4089 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4090
4090
4091 rule_users = relationship('RepoReviewRuleUser')
4091 rule_users = relationship('RepoReviewRuleUser')
4092 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4092 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4093
4093
4094 @hybrid_property
4094 @hybrid_property
4095 def branch_pattern(self):
4095 def branch_pattern(self):
4096 return self._branch_pattern or '*'
4096 return self._branch_pattern or '*'
4097
4097
4098 def _validate_glob(self, value):
4098 def _validate_glob(self, value):
4099 re.compile('^' + glob2re(value) + '$')
4099 re.compile('^' + glob2re(value) + '$')
4100
4100
4101 @branch_pattern.setter
4101 @branch_pattern.setter
4102 def branch_pattern(self, value):
4102 def branch_pattern(self, value):
4103 self._validate_glob(value)
4103 self._validate_glob(value)
4104 self._branch_pattern = value or '*'
4104 self._branch_pattern = value or '*'
4105
4105
4106 @hybrid_property
4106 @hybrid_property
4107 def file_pattern(self):
4107 def file_pattern(self):
4108 return self._file_pattern or '*'
4108 return self._file_pattern or '*'
4109
4109
4110 @file_pattern.setter
4110 @file_pattern.setter
4111 def file_pattern(self, value):
4111 def file_pattern(self, value):
4112 self._validate_glob(value)
4112 self._validate_glob(value)
4113 self._file_pattern = value or '*'
4113 self._file_pattern = value or '*'
4114
4114
4115 def matches(self, branch, files_changed):
4115 def matches(self, branch, files_changed):
4116 """
4116 """
4117 Check if this review rule matches a branch/files in a pull request
4117 Check if this review rule matches a branch/files in a pull request
4118
4118
4119 :param branch: branch name for the commit
4119 :param branch: branch name for the commit
4120 :param files_changed: list of file paths changed in the pull request
4120 :param files_changed: list of file paths changed in the pull request
4121 """
4121 """
4122
4122
4123 branch = branch or ''
4123 branch = branch or ''
4124 files_changed = files_changed or []
4124 files_changed = files_changed or []
4125
4125
4126 branch_matches = True
4126 branch_matches = True
4127 if branch:
4127 if branch:
4128 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4128 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4129 branch_matches = bool(branch_regex.search(branch))
4129 branch_matches = bool(branch_regex.search(branch))
4130
4130
4131 files_matches = True
4131 files_matches = True
4132 if self.file_pattern != '*':
4132 if self.file_pattern != '*':
4133 files_matches = False
4133 files_matches = False
4134 file_regex = re.compile(glob2re(self.file_pattern))
4134 file_regex = re.compile(glob2re(self.file_pattern))
4135 for filename in files_changed:
4135 for filename in files_changed:
4136 if file_regex.search(filename):
4136 if file_regex.search(filename):
4137 files_matches = True
4137 files_matches = True
4138 break
4138 break
4139
4139
4140 return branch_matches and files_matches
4140 return branch_matches and files_matches
4141
4141
4142 @property
4142 @property
4143 def review_users(self):
4143 def review_users(self):
4144 """ Returns the users which this rule applies to """
4144 """ Returns the users which this rule applies to """
4145
4145
4146 users = collections.OrderedDict()
4146 users = collections.OrderedDict()
4147
4147
4148 for rule_user in self.rule_users:
4148 for rule_user in self.rule_users:
4149 if rule_user.user.active:
4149 if rule_user.user.active:
4150 if rule_user.user not in users:
4150 if rule_user.user not in users:
4151 users[rule_user.user.username] = {
4151 users[rule_user.user.username] = {
4152 'user': rule_user.user,
4152 'user': rule_user.user,
4153 'source': 'user',
4153 'source': 'user',
4154 'source_data': {},
4154 'source_data': {},
4155 'data': rule_user.rule_data()
4155 'data': rule_user.rule_data()
4156 }
4156 }
4157
4157
4158 for rule_user_group in self.rule_user_groups:
4158 for rule_user_group in self.rule_user_groups:
4159 source_data = {
4159 source_data = {
4160 'name': rule_user_group.users_group.users_group_name,
4160 'name': rule_user_group.users_group.users_group_name,
4161 'members': len(rule_user_group.users_group.members)
4161 'members': len(rule_user_group.users_group.members)
4162 }
4162 }
4163 for member in rule_user_group.users_group.members:
4163 for member in rule_user_group.users_group.members:
4164 if member.user.active:
4164 if member.user.active:
4165 users[member.user.username] = {
4165 users[member.user.username] = {
4166 'user': member.user,
4166 'user': member.user,
4167 'source': 'user_group',
4167 'source': 'user_group',
4168 'source_data': source_data,
4168 'source_data': source_data,
4169 'data': rule_user_group.rule_data()
4169 'data': rule_user_group.rule_data()
4170 }
4170 }
4171
4171
4172 return users
4172 return users
4173
4173
4174 def __repr__(self):
4174 def __repr__(self):
4175 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4175 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4176 self.repo_review_rule_id, self.repo)
4176 self.repo_review_rule_id, self.repo)
4177
4177
4178
4178
4179 class ScheduleEntry(Base, BaseModel):
4179 class ScheduleEntry(Base, BaseModel):
4180 __tablename__ = 'schedule_entries'
4180 __tablename__ = 'schedule_entries'
4181 __table_args__ = (
4181 __table_args__ = (
4182 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4182 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4183 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4183 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4186 )
4186 )
4187 schedule_types = ['crontab', 'timedelta', 'integer']
4187 schedule_types = ['crontab', 'timedelta', 'integer']
4188 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4188 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4189
4189
4190 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4190 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4191 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4191 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4192 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4192 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4193
4193
4194 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4194 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4195 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4195 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4196
4196
4197 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4197 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4198 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4198 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4199
4199
4200 # task
4200 # task
4201 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4201 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4202 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4202 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4203 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4203 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4204 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4204 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4205
4205
4206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4207 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4207 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4208
4208
4209 @hybrid_property
4209 @hybrid_property
4210 def schedule_type(self):
4210 def schedule_type(self):
4211 return self._schedule_type
4211 return self._schedule_type
4212
4212
4213 @schedule_type.setter
4213 @schedule_type.setter
4214 def schedule_type(self, val):
4214 def schedule_type(self, val):
4215 if val not in self.schedule_types:
4215 if val not in self.schedule_types:
4216 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4216 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4217 val, self.schedule_type))
4217 val, self.schedule_type))
4218
4218
4219 self._schedule_type = val
4219 self._schedule_type = val
4220
4220
4221 @classmethod
4221 @classmethod
4222 def get_uid(cls, obj):
4222 def get_uid(cls, obj):
4223 args = obj.task_args
4223 args = obj.task_args
4224 kwargs = obj.task_kwargs
4224 kwargs = obj.task_kwargs
4225 if isinstance(args, JsonRaw):
4225 if isinstance(args, JsonRaw):
4226 try:
4226 try:
4227 args = json.loads(args)
4227 args = json.loads(args)
4228 except ValueError:
4228 except ValueError:
4229 args = tuple()
4229 args = tuple()
4230
4230
4231 if isinstance(kwargs, JsonRaw):
4231 if isinstance(kwargs, JsonRaw):
4232 try:
4232 try:
4233 kwargs = json.loads(kwargs)
4233 kwargs = json.loads(kwargs)
4234 except ValueError:
4234 except ValueError:
4235 kwargs = dict()
4235 kwargs = dict()
4236
4236
4237 dot_notation = obj.task_dot_notation
4237 dot_notation = obj.task_dot_notation
4238 val = '.'.join(map(safe_str, [
4238 val = '.'.join(map(safe_str, [
4239 sorted(dot_notation), args, sorted(kwargs.items())]))
4239 sorted(dot_notation), args, sorted(kwargs.items())]))
4240 return hashlib.sha1(val).hexdigest()
4240 return hashlib.sha1(val).hexdigest()
4241
4241
4242 @classmethod
4242 @classmethod
4243 def get_by_schedule_name(cls, schedule_name):
4243 def get_by_schedule_name(cls, schedule_name):
4244 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4244 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4245
4245
4246 @classmethod
4246 @classmethod
4247 def get_by_schedule_id(cls, schedule_id):
4247 def get_by_schedule_id(cls, schedule_id):
4248 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4248 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4249
4249
4250 @property
4250 @property
4251 def task(self):
4251 def task(self):
4252 return self.task_dot_notation
4252 return self.task_dot_notation
4253
4253
4254 @property
4254 @property
4255 def schedule(self):
4255 def schedule(self):
4256 from rhodecode.lib.celerylib.utils import raw_2_schedule
4256 from rhodecode.lib.celerylib.utils import raw_2_schedule
4257 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4257 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4258 return schedule
4258 return schedule
4259
4259
4260 @property
4260 @property
4261 def args(self):
4261 def args(self):
4262 try:
4262 try:
4263 return list(self.task_args or [])
4263 return list(self.task_args or [])
4264 except ValueError:
4264 except ValueError:
4265 return list()
4265 return list()
4266
4266
4267 @property
4267 @property
4268 def kwargs(self):
4268 def kwargs(self):
4269 try:
4269 try:
4270 return dict(self.task_kwargs or {})
4270 return dict(self.task_kwargs or {})
4271 except ValueError:
4271 except ValueError:
4272 return dict()
4272 return dict()
4273
4273
4274 def _as_raw(self, val):
4274 def _as_raw(self, val):
4275 if hasattr(val, 'de_coerce'):
4275 if hasattr(val, 'de_coerce'):
4276 val = val.de_coerce()
4276 val = val.de_coerce()
4277 if val:
4277 if val:
4278 val = json.dumps(val)
4278 val = json.dumps(val)
4279
4279
4280 return val
4280 return val
4281
4281
4282 @property
4282 @property
4283 def schedule_definition_raw(self):
4283 def schedule_definition_raw(self):
4284 return self._as_raw(self.schedule_definition)
4284 return self._as_raw(self.schedule_definition)
4285
4285
4286 @property
4286 @property
4287 def args_raw(self):
4287 def args_raw(self):
4288 return self._as_raw(self.task_args)
4288 return self._as_raw(self.task_args)
4289
4289
4290 @property
4290 @property
4291 def kwargs_raw(self):
4291 def kwargs_raw(self):
4292 return self._as_raw(self.task_kwargs)
4292 return self._as_raw(self.task_kwargs)
4293
4293
4294 def __repr__(self):
4294 def __repr__(self):
4295 return '<DB:ScheduleEntry({}:{})>'.format(
4295 return '<DB:ScheduleEntry({}:{})>'.format(
4296 self.schedule_entry_id, self.schedule_name)
4296 self.schedule_entry_id, self.schedule_name)
4297
4297
4298
4298
4299 @event.listens_for(ScheduleEntry, 'before_update')
4299 @event.listens_for(ScheduleEntry, 'before_update')
4300 def update_task_uid(mapper, connection, target):
4300 def update_task_uid(mapper, connection, target):
4301 target.task_uid = ScheduleEntry.get_uid(target)
4301 target.task_uid = ScheduleEntry.get_uid(target)
4302
4302
4303
4303
4304 @event.listens_for(ScheduleEntry, 'before_insert')
4304 @event.listens_for(ScheduleEntry, 'before_insert')
4305 def set_task_uid(mapper, connection, target):
4305 def set_task_uid(mapper, connection, target):
4306 target.task_uid = ScheduleEntry.get_uid(target)
4306 target.task_uid = ScheduleEntry.get_uid(target)
4307
4307
4308
4308
4309 class DbMigrateVersion(Base, BaseModel):
4309 class DbMigrateVersion(Base, BaseModel):
4310 __tablename__ = 'db_migrate_version'
4310 __tablename__ = 'db_migrate_version'
4311 __table_args__ = (
4311 __table_args__ = (
4312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4314 )
4314 )
4315 repository_id = Column('repository_id', String(250), primary_key=True)
4315 repository_id = Column('repository_id', String(250), primary_key=True)
4316 repository_path = Column('repository_path', Text)
4316 repository_path = Column('repository_path', Text)
4317 version = Column('version', Integer)
4317 version = Column('version', Integer)
4318
4318
4319
4319
4320 class DbSession(Base, BaseModel):
4320 class DbSession(Base, BaseModel):
4321 __tablename__ = 'db_session'
4321 __tablename__ = 'db_session'
4322 __table_args__ = (
4322 __table_args__ = (
4323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4324 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4324 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4325 )
4325 )
4326
4326
4327 def __repr__(self):
4327 def __repr__(self):
4328 return '<DB:DbSession({})>'.format(self.id)
4328 return '<DB:DbSession({})>'.format(self.id)
4329
4329
4330 id = Column('id', Integer())
4330 id = Column('id', Integer())
4331 namespace = Column('namespace', String(255), primary_key=True)
4331 namespace = Column('namespace', String(255), primary_key=True)
4332 accessed = Column('accessed', DateTime, nullable=False)
4332 accessed = Column('accessed', DateTime, nullable=False)
4333 created = Column('created', DateTime, nullable=False)
4333 created = Column('created', DateTime, nullable=False)
4334 data = Column('data', PickleType, nullable=False)
4334 data = Column('data', PickleType, nullable=False)
@@ -1,4587 +1,4587 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, 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, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @LazyProperty
679 @LazyProperty
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self, cache=True):
683 def get_feed_token(self, cache=True):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
687 if cache:
687 if cache:
688 feed_tokens = feed_tokens.options(
688 feed_tokens = feed_tokens.options(
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
690
690
691 feed_tokens = feed_tokens.all()
691 feed_tokens = feed_tokens.all()
692 if feed_tokens:
692 if feed_tokens:
693 return feed_tokens[0].api_key
693 return feed_tokens[0].api_key
694 return 'NO_FEED_TOKEN_AVAILABLE'
694 return 'NO_FEED_TOKEN_AVAILABLE'
695
695
696 @classmethod
696 @classmethod
697 def get(cls, user_id, cache=False):
697 def get(cls, user_id, cache=False):
698 if not user_id:
698 if not user_id:
699 return
699 return
700
700
701 user = cls.query()
701 user = cls.query()
702 if cache:
702 if cache:
703 user = user.options(
703 user = user.options(
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
705 return user.get(user_id)
705 return user.get(user_id)
706
706
707 @classmethod
707 @classmethod
708 def extra_valid_auth_tokens(cls, user, role=None):
708 def extra_valid_auth_tokens(cls, user, role=None):
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
710 .filter(or_(UserApiKeys.expires == -1,
710 .filter(or_(UserApiKeys.expires == -1,
711 UserApiKeys.expires >= time.time()))
711 UserApiKeys.expires >= time.time()))
712 if role:
712 if role:
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
715 return tokens.all()
715 return tokens.all()
716
716
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
718 from rhodecode.lib import auth
718 from rhodecode.lib import auth
719
719
720 log.debug('Trying to authenticate user: %s via auth-token, '
720 log.debug('Trying to authenticate user: %s via auth-token, '
721 'and roles: %s', self, roles)
721 'and roles: %s', self, roles)
722
722
723 if not auth_token:
723 if not auth_token:
724 return False
724 return False
725
725
726 crypto_backend = auth.crypto_backend()
726 crypto_backend = auth.crypto_backend()
727
727
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
729 tokens_q = UserApiKeys.query()\
729 tokens_q = UserApiKeys.query()\
730 .filter(UserApiKeys.user_id == self.user_id)\
730 .filter(UserApiKeys.user_id == self.user_id)\
731 .filter(or_(UserApiKeys.expires == -1,
731 .filter(or_(UserApiKeys.expires == -1,
732 UserApiKeys.expires >= time.time()))
732 UserApiKeys.expires >= time.time()))
733
733
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
735
735
736 plain_tokens = []
736 plain_tokens = []
737 hash_tokens = []
737 hash_tokens = []
738
738
739 for token in tokens_q.all():
739 for token in tokens_q.all():
740 # verify scope first
740 # verify scope first
741 if token.repo_id:
741 if token.repo_id:
742 # token has a scope, we need to verify it
742 # token has a scope, we need to verify it
743 if scope_repo_id != token.repo_id:
743 if scope_repo_id != token.repo_id:
744 log.debug(
744 log.debug(
745 'Scope mismatch: token has a set repo scope: %s, '
745 'Scope mismatch: token has a set repo scope: %s, '
746 'and calling scope is:%s, skipping further checks',
746 'and calling scope is:%s, skipping further checks',
747 token.repo, scope_repo_id)
747 token.repo, scope_repo_id)
748 # token has a scope, and it doesn't match, skip token
748 # token has a scope, and it doesn't match, skip token
749 continue
749 continue
750
750
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
752 hash_tokens.append(token.api_key)
752 hash_tokens.append(token.api_key)
753 else:
753 else:
754 plain_tokens.append(token.api_key)
754 plain_tokens.append(token.api_key)
755
755
756 is_plain_match = auth_token in plain_tokens
756 is_plain_match = auth_token in plain_tokens
757 if is_plain_match:
757 if is_plain_match:
758 return True
758 return True
759
759
760 for hashed in hash_tokens:
760 for hashed in hash_tokens:
761 # TODO(marcink): this is expensive to calculate, but most secure
761 # TODO(marcink): this is expensive to calculate, but most secure
762 match = crypto_backend.hash_check(auth_token, hashed)
762 match = crypto_backend.hash_check(auth_token, hashed)
763 if match:
763 if match:
764 return True
764 return True
765
765
766 return False
766 return False
767
767
768 @property
768 @property
769 def ip_addresses(self):
769 def ip_addresses(self):
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
771 return [x.ip_addr for x in ret]
771 return [x.ip_addr for x in ret]
772
772
773 @property
773 @property
774 def username_and_name(self):
774 def username_and_name(self):
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
776
776
777 @property
777 @property
778 def username_or_name_or_email(self):
778 def username_or_name_or_email(self):
779 full_name = self.full_name if self.full_name is not ' ' else None
779 full_name = self.full_name if self.full_name is not ' ' else None
780 return self.username or full_name or self.email
780 return self.username or full_name or self.email
781
781
782 @property
782 @property
783 def full_name(self):
783 def full_name(self):
784 return '%s %s' % (self.first_name, self.last_name)
784 return '%s %s' % (self.first_name, self.last_name)
785
785
786 @property
786 @property
787 def full_name_or_username(self):
787 def full_name_or_username(self):
788 return ('%s %s' % (self.first_name, self.last_name)
788 return ('%s %s' % (self.first_name, self.last_name)
789 if (self.first_name and self.last_name) else self.username)
789 if (self.first_name and self.last_name) else self.username)
790
790
791 @property
791 @property
792 def full_contact(self):
792 def full_contact(self):
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
794
794
795 @property
795 @property
796 def short_contact(self):
796 def short_contact(self):
797 return '%s %s' % (self.first_name, self.last_name)
797 return '%s %s' % (self.first_name, self.last_name)
798
798
799 @property
799 @property
800 def is_admin(self):
800 def is_admin(self):
801 return self.admin
801 return self.admin
802
802
803 def AuthUser(self, **kwargs):
803 def AuthUser(self, **kwargs):
804 """
804 """
805 Returns instance of AuthUser for this user
805 Returns instance of AuthUser for this user
806 """
806 """
807 from rhodecode.lib.auth import AuthUser
807 from rhodecode.lib.auth import AuthUser
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
809
809
810 @hybrid_property
810 @hybrid_property
811 def user_data(self):
811 def user_data(self):
812 if not self._user_data:
812 if not self._user_data:
813 return {}
813 return {}
814
814
815 try:
815 try:
816 return json.loads(self._user_data)
816 return json.loads(self._user_data)
817 except TypeError:
817 except TypeError:
818 return {}
818 return {}
819
819
820 @user_data.setter
820 @user_data.setter
821 def user_data(self, val):
821 def user_data(self, val):
822 if not isinstance(val, dict):
822 if not isinstance(val, dict):
823 raise Exception('user_data must be dict, got %s' % type(val))
823 raise Exception('user_data must be dict, got %s' % type(val))
824 try:
824 try:
825 self._user_data = json.dumps(val)
825 self._user_data = json.dumps(val)
826 except Exception:
826 except Exception:
827 log.error(traceback.format_exc())
827 log.error(traceback.format_exc())
828
828
829 @classmethod
829 @classmethod
830 def get_by_username(cls, username, case_insensitive=False,
830 def get_by_username(cls, username, case_insensitive=False,
831 cache=False, identity_cache=False):
831 cache=False, identity_cache=False):
832 session = Session()
832 session = Session()
833
833
834 if case_insensitive:
834 if case_insensitive:
835 q = cls.query().filter(
835 q = cls.query().filter(
836 func.lower(cls.username) == func.lower(username))
836 func.lower(cls.username) == func.lower(username))
837 else:
837 else:
838 q = cls.query().filter(cls.username == username)
838 q = cls.query().filter(cls.username == username)
839
839
840 if cache:
840 if cache:
841 if identity_cache:
841 if identity_cache:
842 val = cls.identity_cache(session, 'username', username)
842 val = cls.identity_cache(session, 'username', username)
843 if val:
843 if val:
844 return val
844 return val
845 else:
845 else:
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
847 q = q.options(
847 q = q.options(
848 FromCache("sql_cache_short", cache_key))
848 FromCache("sql_cache_short", cache_key))
849
849
850 return q.scalar()
850 return q.scalar()
851
851
852 @classmethod
852 @classmethod
853 def get_by_auth_token(cls, auth_token, cache=False):
853 def get_by_auth_token(cls, auth_token, cache=False):
854 q = UserApiKeys.query()\
854 q = UserApiKeys.query()\
855 .filter(UserApiKeys.api_key == auth_token)\
855 .filter(UserApiKeys.api_key == auth_token)\
856 .filter(or_(UserApiKeys.expires == -1,
856 .filter(or_(UserApiKeys.expires == -1,
857 UserApiKeys.expires >= time.time()))
857 UserApiKeys.expires >= time.time()))
858 if cache:
858 if cache:
859 q = q.options(
859 q = q.options(
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
861
861
862 match = q.first()
862 match = q.first()
863 if match:
863 if match:
864 return match.user
864 return match.user
865
865
866 @classmethod
866 @classmethod
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
868
868
869 if case_insensitive:
869 if case_insensitive:
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
871
871
872 else:
872 else:
873 q = cls.query().filter(cls.email == email)
873 q = cls.query().filter(cls.email == email)
874
874
875 email_key = _hash_key(email)
875 email_key = _hash_key(email)
876 if cache:
876 if cache:
877 q = q.options(
877 q = q.options(
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
879
879
880 ret = q.scalar()
880 ret = q.scalar()
881 if ret is None:
881 if ret is None:
882 q = UserEmailMap.query()
882 q = UserEmailMap.query()
883 # try fetching in alternate email map
883 # try fetching in alternate email map
884 if case_insensitive:
884 if case_insensitive:
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
886 else:
886 else:
887 q = q.filter(UserEmailMap.email == email)
887 q = q.filter(UserEmailMap.email == email)
888 q = q.options(joinedload(UserEmailMap.user))
888 q = q.options(joinedload(UserEmailMap.user))
889 if cache:
889 if cache:
890 q = q.options(
890 q = q.options(
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
892 ret = getattr(q.scalar(), 'user', None)
892 ret = getattr(q.scalar(), 'user', None)
893
893
894 return ret
894 return ret
895
895
896 @classmethod
896 @classmethod
897 def get_from_cs_author(cls, author):
897 def get_from_cs_author(cls, author):
898 """
898 """
899 Tries to get User objects out of commit author string
899 Tries to get User objects out of commit author string
900
900
901 :param author:
901 :param author:
902 """
902 """
903 from rhodecode.lib.helpers import email, author_name
903 from rhodecode.lib.helpers import email, author_name
904 # Valid email in the attribute passed, see if they're in the system
904 # Valid email in the attribute passed, see if they're in the system
905 _email = email(author)
905 _email = email(author)
906 if _email:
906 if _email:
907 user = cls.get_by_email(_email, case_insensitive=True)
907 user = cls.get_by_email(_email, case_insensitive=True)
908 if user:
908 if user:
909 return user
909 return user
910 # Maybe we can match by username?
910 # Maybe we can match by username?
911 _author = author_name(author)
911 _author = author_name(author)
912 user = cls.get_by_username(_author, case_insensitive=True)
912 user = cls.get_by_username(_author, case_insensitive=True)
913 if user:
913 if user:
914 return user
914 return user
915
915
916 def update_userdata(self, **kwargs):
916 def update_userdata(self, **kwargs):
917 usr = self
917 usr = self
918 old = usr.user_data
918 old = usr.user_data
919 old.update(**kwargs)
919 old.update(**kwargs)
920 usr.user_data = old
920 usr.user_data = old
921 Session().add(usr)
921 Session().add(usr)
922 log.debug('updated userdata with ', kwargs)
922 log.debug('updated userdata with ', kwargs)
923
923
924 def update_lastlogin(self):
924 def update_lastlogin(self):
925 """Update user lastlogin"""
925 """Update user lastlogin"""
926 self.last_login = datetime.datetime.now()
926 self.last_login = datetime.datetime.now()
927 Session().add(self)
927 Session().add(self)
928 log.debug('updated user %s lastlogin', self.username)
928 log.debug('updated user %s lastlogin', self.username)
929
929
930 def update_lastactivity(self):
930 def update_lastactivity(self):
931 """Update user lastactivity"""
931 """Update user lastactivity"""
932 self.last_activity = datetime.datetime.now()
932 self.last_activity = datetime.datetime.now()
933 Session().add(self)
933 Session().add(self)
934 log.debug('updated user `%s` last activity', self.username)
934 log.debug('updated user `%s` last activity', self.username)
935
935
936 def update_password(self, new_password):
936 def update_password(self, new_password):
937 from rhodecode.lib.auth import get_crypt_password
937 from rhodecode.lib.auth import get_crypt_password
938
938
939 self.password = get_crypt_password(new_password)
939 self.password = get_crypt_password(new_password)
940 Session().add(self)
940 Session().add(self)
941
941
942 @classmethod
942 @classmethod
943 def get_first_super_admin(cls):
943 def get_first_super_admin(cls):
944 user = User.query().filter(User.admin == true()).first()
944 user = User.query().filter(User.admin == true()).first()
945 if user is None:
945 if user is None:
946 raise Exception('FATAL: Missing administrative account!')
946 raise Exception('FATAL: Missing administrative account!')
947 return user
947 return user
948
948
949 @classmethod
949 @classmethod
950 def get_all_super_admins(cls):
950 def get_all_super_admins(cls):
951 """
951 """
952 Returns all admin accounts sorted by username
952 Returns all admin accounts sorted by username
953 """
953 """
954 return User.query().filter(User.admin == true())\
954 return User.query().filter(User.admin == true())\
955 .order_by(User.username.asc()).all()
955 .order_by(User.username.asc()).all()
956
956
957 @classmethod
957 @classmethod
958 def get_default_user(cls, cache=False, refresh=False):
958 def get_default_user(cls, cache=False, refresh=False):
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
960 if user is None:
960 if user is None:
961 raise Exception('FATAL: Missing default account!')
961 raise Exception('FATAL: Missing default account!')
962 if refresh:
962 if refresh:
963 # The default user might be based on outdated state which
963 # The default user might be based on outdated state which
964 # has been loaded from the cache.
964 # has been loaded from the cache.
965 # A call to refresh() ensures that the
965 # A call to refresh() ensures that the
966 # latest state from the database is used.
966 # latest state from the database is used.
967 Session().refresh(user)
967 Session().refresh(user)
968 return user
968 return user
969
969
970 def _get_default_perms(self, user, suffix=''):
970 def _get_default_perms(self, user, suffix=''):
971 from rhodecode.model.permission import PermissionModel
971 from rhodecode.model.permission import PermissionModel
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
973
973
974 def get_default_perms(self, suffix=''):
974 def get_default_perms(self, suffix=''):
975 return self._get_default_perms(self, suffix)
975 return self._get_default_perms(self, suffix)
976
976
977 def get_api_data(self, include_secrets=False, details='full'):
977 def get_api_data(self, include_secrets=False, details='full'):
978 """
978 """
979 Common function for generating user related data for API
979 Common function for generating user related data for API
980
980
981 :param include_secrets: By default secrets in the API data will be replaced
981 :param include_secrets: By default secrets in the API data will be replaced
982 by a placeholder value to prevent exposing this data by accident. In case
982 by a placeholder value to prevent exposing this data by accident. In case
983 this data shall be exposed, set this flag to ``True``.
983 this data shall be exposed, set this flag to ``True``.
984
984
985 :param details: details can be 'basic|full' basic gives only a subset of
985 :param details: details can be 'basic|full' basic gives only a subset of
986 the available user information that includes user_id, name and emails.
986 the available user information that includes user_id, name and emails.
987 """
987 """
988 user = self
988 user = self
989 user_data = self.user_data
989 user_data = self.user_data
990 data = {
990 data = {
991 'user_id': user.user_id,
991 'user_id': user.user_id,
992 'username': user.username,
992 'username': user.username,
993 'firstname': user.name,
993 'firstname': user.name,
994 'lastname': user.lastname,
994 'lastname': user.lastname,
995 'email': user.email,
995 'email': user.email,
996 'emails': user.emails,
996 'emails': user.emails,
997 }
997 }
998 if details == 'basic':
998 if details == 'basic':
999 return data
999 return data
1000
1000
1001 auth_token_length = 40
1001 auth_token_length = 40
1002 auth_token_replacement = '*' * auth_token_length
1002 auth_token_replacement = '*' * auth_token_length
1003
1003
1004 extras = {
1004 extras = {
1005 'auth_tokens': [auth_token_replacement],
1005 'auth_tokens': [auth_token_replacement],
1006 'active': user.active,
1006 'active': user.active,
1007 'admin': user.admin,
1007 'admin': user.admin,
1008 'extern_type': user.extern_type,
1008 'extern_type': user.extern_type,
1009 'extern_name': user.extern_name,
1009 'extern_name': user.extern_name,
1010 'last_login': user.last_login,
1010 'last_login': user.last_login,
1011 'last_activity': user.last_activity,
1011 'last_activity': user.last_activity,
1012 'ip_addresses': user.ip_addresses,
1012 'ip_addresses': user.ip_addresses,
1013 'language': user_data.get('language')
1013 'language': user_data.get('language')
1014 }
1014 }
1015 data.update(extras)
1015 data.update(extras)
1016
1016
1017 if include_secrets:
1017 if include_secrets:
1018 data['auth_tokens'] = user.auth_tokens
1018 data['auth_tokens'] = user.auth_tokens
1019 return data
1019 return data
1020
1020
1021 def __json__(self):
1021 def __json__(self):
1022 data = {
1022 data = {
1023 'full_name': self.full_name,
1023 'full_name': self.full_name,
1024 'full_name_or_username': self.full_name_or_username,
1024 'full_name_or_username': self.full_name_or_username,
1025 'short_contact': self.short_contact,
1025 'short_contact': self.short_contact,
1026 'full_contact': self.full_contact,
1026 'full_contact': self.full_contact,
1027 }
1027 }
1028 data.update(self.get_api_data())
1028 data.update(self.get_api_data())
1029 return data
1029 return data
1030
1030
1031
1031
1032 class UserApiKeys(Base, BaseModel):
1032 class UserApiKeys(Base, BaseModel):
1033 __tablename__ = 'user_api_keys'
1033 __tablename__ = 'user_api_keys'
1034 __table_args__ = (
1034 __table_args__ = (
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1039 )
1039 )
1040 __mapper_args__ = {}
1040 __mapper_args__ = {}
1041
1041
1042 # ApiKey role
1042 # ApiKey role
1043 ROLE_ALL = 'token_role_all'
1043 ROLE_ALL = 'token_role_all'
1044 ROLE_HTTP = 'token_role_http'
1044 ROLE_HTTP = 'token_role_http'
1045 ROLE_VCS = 'token_role_vcs'
1045 ROLE_VCS = 'token_role_vcs'
1046 ROLE_API = 'token_role_api'
1046 ROLE_API = 'token_role_api'
1047 ROLE_FEED = 'token_role_feed'
1047 ROLE_FEED = 'token_role_feed'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1049
1049
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1051
1051
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1056 expires = Column('expires', Float(53), nullable=False)
1056 expires = Column('expires', Float(53), nullable=False)
1057 role = Column('role', String(255), nullable=True)
1057 role = Column('role', String(255), nullable=True)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1059
1059
1060 # scope columns
1060 # scope columns
1061 repo_id = Column(
1061 repo_id = Column(
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1063 nullable=True, unique=None, default=None)
1063 nullable=True, unique=None, default=None)
1064 repo = relationship('Repository', lazy='joined')
1064 repo = relationship('Repository', lazy='joined')
1065
1065
1066 repo_group_id = Column(
1066 repo_group_id = Column(
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1068 nullable=True, unique=None, default=None)
1068 nullable=True, unique=None, default=None)
1069 repo_group = relationship('RepoGroup', lazy='joined')
1069 repo_group = relationship('RepoGroup', lazy='joined')
1070
1070
1071 user = relationship('User', lazy='joined')
1071 user = relationship('User', lazy='joined')
1072
1072
1073 def __unicode__(self):
1073 def __unicode__(self):
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1075
1075
1076 def __json__(self):
1076 def __json__(self):
1077 data = {
1077 data = {
1078 'auth_token': self.api_key,
1078 'auth_token': self.api_key,
1079 'role': self.role,
1079 'role': self.role,
1080 'scope': self.scope_humanized,
1080 'scope': self.scope_humanized,
1081 'expired': self.expired
1081 'expired': self.expired
1082 }
1082 }
1083 return data
1083 return data
1084
1084
1085 def get_api_data(self, include_secrets=False):
1085 def get_api_data(self, include_secrets=False):
1086 data = self.__json__()
1086 data = self.__json__()
1087 if include_secrets:
1087 if include_secrets:
1088 return data
1088 return data
1089 else:
1089 else:
1090 data['auth_token'] = self.token_obfuscated
1090 data['auth_token'] = self.token_obfuscated
1091 return data
1091 return data
1092
1092
1093 @hybrid_property
1093 @hybrid_property
1094 def description_safe(self):
1094 def description_safe(self):
1095 from rhodecode.lib import helpers as h
1095 from rhodecode.lib import helpers as h
1096 return h.escape(self.description)
1096 return h.escape(self.description)
1097
1097
1098 @property
1098 @property
1099 def expired(self):
1099 def expired(self):
1100 if self.expires == -1:
1100 if self.expires == -1:
1101 return False
1101 return False
1102 return time.time() > self.expires
1102 return time.time() > self.expires
1103
1103
1104 @classmethod
1104 @classmethod
1105 def _get_role_name(cls, role):
1105 def _get_role_name(cls, role):
1106 return {
1106 return {
1107 cls.ROLE_ALL: _('all'),
1107 cls.ROLE_ALL: _('all'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1110 cls.ROLE_API: _('api calls'),
1110 cls.ROLE_API: _('api calls'),
1111 cls.ROLE_FEED: _('feed access'),
1111 cls.ROLE_FEED: _('feed access'),
1112 }.get(role, role)
1112 }.get(role, role)
1113
1113
1114 @property
1114 @property
1115 def role_humanized(self):
1115 def role_humanized(self):
1116 return self._get_role_name(self.role)
1116 return self._get_role_name(self.role)
1117
1117
1118 def _get_scope(self):
1118 def _get_scope(self):
1119 if self.repo:
1119 if self.repo:
1120 return repr(self.repo)
1120 return repr(self.repo)
1121 if self.repo_group:
1121 if self.repo_group:
1122 return repr(self.repo_group) + ' (recursive)'
1122 return repr(self.repo_group) + ' (recursive)'
1123 return 'global'
1123 return 'global'
1124
1124
1125 @property
1125 @property
1126 def scope_humanized(self):
1126 def scope_humanized(self):
1127 return self._get_scope()
1127 return self._get_scope()
1128
1128
1129 @property
1129 @property
1130 def token_obfuscated(self):
1130 def token_obfuscated(self):
1131 if self.api_key:
1131 if self.api_key:
1132 return self.api_key[:4] + "****"
1132 return self.api_key[:4] + "****"
1133
1133
1134
1134
1135 class UserEmailMap(Base, BaseModel):
1135 class UserEmailMap(Base, BaseModel):
1136 __tablename__ = 'user_email_map'
1136 __tablename__ = 'user_email_map'
1137 __table_args__ = (
1137 __table_args__ = (
1138 Index('uem_email_idx', 'email'),
1138 Index('uem_email_idx', 'email'),
1139 UniqueConstraint('email'),
1139 UniqueConstraint('email'),
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1142 )
1142 )
1143 __mapper_args__ = {}
1143 __mapper_args__ = {}
1144
1144
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1148 user = relationship('User', lazy='joined')
1148 user = relationship('User', lazy='joined')
1149
1149
1150 @validates('_email')
1150 @validates('_email')
1151 def validate_email(self, key, email):
1151 def validate_email(self, key, email):
1152 # check if this email is not main one
1152 # check if this email is not main one
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1154 if main_email is not None:
1154 if main_email is not None:
1155 raise AttributeError('email %s is present is user table' % email)
1155 raise AttributeError('email %s is present is user table' % email)
1156 return email
1156 return email
1157
1157
1158 @hybrid_property
1158 @hybrid_property
1159 def email(self):
1159 def email(self):
1160 return self._email
1160 return self._email
1161
1161
1162 @email.setter
1162 @email.setter
1163 def email(self, val):
1163 def email(self, val):
1164 self._email = val.lower() if val else None
1164 self._email = val.lower() if val else None
1165
1165
1166
1166
1167 class UserIpMap(Base, BaseModel):
1167 class UserIpMap(Base, BaseModel):
1168 __tablename__ = 'user_ip_map'
1168 __tablename__ = 'user_ip_map'
1169 __table_args__ = (
1169 __table_args__ = (
1170 UniqueConstraint('user_id', 'ip_addr'),
1170 UniqueConstraint('user_id', 'ip_addr'),
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1173 )
1173 )
1174 __mapper_args__ = {}
1174 __mapper_args__ = {}
1175
1175
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1181 user = relationship('User', lazy='joined')
1181 user = relationship('User', lazy='joined')
1182
1182
1183 @hybrid_property
1183 @hybrid_property
1184 def description_safe(self):
1184 def description_safe(self):
1185 from rhodecode.lib import helpers as h
1185 from rhodecode.lib import helpers as h
1186 return h.escape(self.description)
1186 return h.escape(self.description)
1187
1187
1188 @classmethod
1188 @classmethod
1189 def _get_ip_range(cls, ip_addr):
1189 def _get_ip_range(cls, ip_addr):
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1191 return [str(net.network_address), str(net.broadcast_address)]
1191 return [str(net.network_address), str(net.broadcast_address)]
1192
1192
1193 def __json__(self):
1193 def __json__(self):
1194 return {
1194 return {
1195 'ip_addr': self.ip_addr,
1195 'ip_addr': self.ip_addr,
1196 'ip_range': self._get_ip_range(self.ip_addr),
1196 'ip_range': self._get_ip_range(self.ip_addr),
1197 }
1197 }
1198
1198
1199 def __unicode__(self):
1199 def __unicode__(self):
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1201 self.user_id, self.ip_addr)
1201 self.user_id, self.ip_addr)
1202
1202
1203
1203
1204 class UserSshKeys(Base, BaseModel):
1204 class UserSshKeys(Base, BaseModel):
1205 __tablename__ = 'user_ssh_keys'
1205 __tablename__ = 'user_ssh_keys'
1206 __table_args__ = (
1206 __table_args__ = (
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1208
1208
1209 UniqueConstraint('ssh_key_fingerprint'),
1209 UniqueConstraint('ssh_key_fingerprint'),
1210
1210
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1219
1219
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1221
1221
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1225
1225
1226 user = relationship('User', lazy='joined')
1226 user = relationship('User', lazy='joined')
1227
1227
1228 def __json__(self):
1228 def __json__(self):
1229 data = {
1229 data = {
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1231 'description': self.description,
1231 'description': self.description,
1232 'created_on': self.created_on
1232 'created_on': self.created_on
1233 }
1233 }
1234 return data
1234 return data
1235
1235
1236 def get_api_data(self):
1236 def get_api_data(self):
1237 data = self.__json__()
1237 data = self.__json__()
1238 return data
1238 return data
1239
1239
1240
1240
1241 class UserLog(Base, BaseModel):
1241 class UserLog(Base, BaseModel):
1242 __tablename__ = 'user_logs'
1242 __tablename__ = 'user_logs'
1243 __table_args__ = (
1243 __table_args__ = (
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1246 )
1246 )
1247 VERSION_1 = 'v1'
1247 VERSION_1 = 'v1'
1248 VERSION_2 = 'v2'
1248 VERSION_2 = 'v2'
1249 VERSIONS = [VERSION_1, VERSION_2]
1249 VERSIONS = [VERSION_1, VERSION_2]
1250
1250
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1259
1259
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1263
1263
1264 def __unicode__(self):
1264 def __unicode__(self):
1265 return u"<%s('id:%s:%s')>" % (
1265 return u"<%s('id:%s:%s')>" % (
1266 self.__class__.__name__, self.repository_name, self.action)
1266 self.__class__.__name__, self.repository_name, self.action)
1267
1267
1268 def __json__(self):
1268 def __json__(self):
1269 return {
1269 return {
1270 'user_id': self.user_id,
1270 'user_id': self.user_id,
1271 'username': self.username,
1271 'username': self.username,
1272 'repository_id': self.repository_id,
1272 'repository_id': self.repository_id,
1273 'repository_name': self.repository_name,
1273 'repository_name': self.repository_name,
1274 'user_ip': self.user_ip,
1274 'user_ip': self.user_ip,
1275 'action_date': self.action_date,
1275 'action_date': self.action_date,
1276 'action': self.action,
1276 'action': self.action,
1277 }
1277 }
1278
1278
1279 @hybrid_property
1279 @hybrid_property
1280 def entry_id(self):
1280 def entry_id(self):
1281 return self.user_log_id
1281 return self.user_log_id
1282
1282
1283 @property
1283 @property
1284 def action_as_day(self):
1284 def action_as_day(self):
1285 return datetime.date(*self.action_date.timetuple()[:3])
1285 return datetime.date(*self.action_date.timetuple()[:3])
1286
1286
1287 user = relationship('User')
1287 user = relationship('User')
1288 repository = relationship('Repository', cascade='')
1288 repository = relationship('Repository', cascade='')
1289
1289
1290
1290
1291 class UserGroup(Base, BaseModel):
1291 class UserGroup(Base, BaseModel):
1292 __tablename__ = 'users_groups'
1292 __tablename__ = 'users_groups'
1293 __table_args__ = (
1293 __table_args__ = (
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1296 )
1296 )
1297
1297
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1306
1306
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1313
1313
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1316
1316
1317 @classmethod
1317 @classmethod
1318 def _load_group_data(cls, column):
1318 def _load_group_data(cls, column):
1319 if not column:
1319 if not column:
1320 return {}
1320 return {}
1321
1321
1322 try:
1322 try:
1323 return json.loads(column) or {}
1323 return json.loads(column) or {}
1324 except TypeError:
1324 except TypeError:
1325 return {}
1325 return {}
1326
1326
1327 @hybrid_property
1327 @hybrid_property
1328 def description_safe(self):
1328 def description_safe(self):
1329 from rhodecode.lib import helpers as h
1329 from rhodecode.lib import helpers as h
1330 return h.escape(self.user_group_description)
1330 return h.escape(self.user_group_description)
1331
1331
1332 @hybrid_property
1332 @hybrid_property
1333 def group_data(self):
1333 def group_data(self):
1334 return self._load_group_data(self._group_data)
1334 return self._load_group_data(self._group_data)
1335
1335
1336 @group_data.expression
1336 @group_data.expression
1337 def group_data(self, **kwargs):
1337 def group_data(self, **kwargs):
1338 return self._group_data
1338 return self._group_data
1339
1339
1340 @group_data.setter
1340 @group_data.setter
1341 def group_data(self, val):
1341 def group_data(self, val):
1342 try:
1342 try:
1343 self._group_data = json.dumps(val)
1343 self._group_data = json.dumps(val)
1344 except Exception:
1344 except Exception:
1345 log.error(traceback.format_exc())
1345 log.error(traceback.format_exc())
1346
1346
1347 @classmethod
1347 @classmethod
1348 def _load_sync(cls, group_data):
1348 def _load_sync(cls, group_data):
1349 if group_data:
1349 if group_data:
1350 return group_data.get('extern_type')
1350 return group_data.get('extern_type')
1351
1351
1352 @property
1352 @property
1353 def sync(self):
1353 def sync(self):
1354 return self._load_sync(self.group_data)
1354 return self._load_sync(self.group_data)
1355
1355
1356 def __unicode__(self):
1356 def __unicode__(self):
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1358 self.users_group_id,
1358 self.users_group_id,
1359 self.users_group_name)
1359 self.users_group_name)
1360
1360
1361 @classmethod
1361 @classmethod
1362 def get_by_group_name(cls, group_name, cache=False,
1362 def get_by_group_name(cls, group_name, cache=False,
1363 case_insensitive=False):
1363 case_insensitive=False):
1364 if case_insensitive:
1364 if case_insensitive:
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1366 func.lower(group_name))
1366 func.lower(group_name))
1367
1367
1368 else:
1368 else:
1369 q = cls.query().filter(cls.users_group_name == group_name)
1369 q = cls.query().filter(cls.users_group_name == group_name)
1370 if cache:
1370 if cache:
1371 q = q.options(
1371 q = q.options(
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1373 return q.scalar()
1373 return q.scalar()
1374
1374
1375 @classmethod
1375 @classmethod
1376 def get(cls, user_group_id, cache=False):
1376 def get(cls, user_group_id, cache=False):
1377 if not user_group_id:
1377 if not user_group_id:
1378 return
1378 return
1379
1379
1380 user_group = cls.query()
1380 user_group = cls.query()
1381 if cache:
1381 if cache:
1382 user_group = user_group.options(
1382 user_group = user_group.options(
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1384 return user_group.get(user_group_id)
1384 return user_group.get(user_group_id)
1385
1385
1386 def permissions(self, with_admins=True, with_owner=True):
1386 def permissions(self, with_admins=True, with_owner=True):
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1389 joinedload(UserUserGroupToPerm.user),
1389 joinedload(UserUserGroupToPerm.user),
1390 joinedload(UserUserGroupToPerm.permission),)
1390 joinedload(UserUserGroupToPerm.permission),)
1391
1391
1392 # get owners and admins and permissions. We do a trick of re-writing
1392 # get owners and admins and permissions. We do a trick of re-writing
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1394 # has a global reference and changing one object propagates to all
1394 # has a global reference and changing one object propagates to all
1395 # others. This means if admin is also an owner admin_row that change
1395 # others. This means if admin is also an owner admin_row that change
1396 # would propagate to both objects
1396 # would propagate to both objects
1397 perm_rows = []
1397 perm_rows = []
1398 for _usr in q.all():
1398 for _usr in q.all():
1399 usr = AttributeDict(_usr.user.get_dict())
1399 usr = AttributeDict(_usr.user.get_dict())
1400 usr.permission = _usr.permission.permission_name
1400 usr.permission = _usr.permission.permission_name
1401 perm_rows.append(usr)
1401 perm_rows.append(usr)
1402
1402
1403 # filter the perm rows by 'default' first and then sort them by
1403 # filter the perm rows by 'default' first and then sort them by
1404 # admin,write,read,none permissions sorted again alphabetically in
1404 # admin,write,read,none permissions sorted again alphabetically in
1405 # each group
1405 # each group
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1407
1407
1408 _admin_perm = 'usergroup.admin'
1408 _admin_perm = 'usergroup.admin'
1409 owner_row = []
1409 owner_row = []
1410 if with_owner:
1410 if with_owner:
1411 usr = AttributeDict(self.user.get_dict())
1411 usr = AttributeDict(self.user.get_dict())
1412 usr.owner_row = True
1412 usr.owner_row = True
1413 usr.permission = _admin_perm
1413 usr.permission = _admin_perm
1414 owner_row.append(usr)
1414 owner_row.append(usr)
1415
1415
1416 super_admin_rows = []
1416 super_admin_rows = []
1417 if with_admins:
1417 if with_admins:
1418 for usr in User.get_all_super_admins():
1418 for usr in User.get_all_super_admins():
1419 # if this admin is also owner, don't double the record
1419 # if this admin is also owner, don't double the record
1420 if usr.user_id == owner_row[0].user_id:
1420 if usr.user_id == owner_row[0].user_id:
1421 owner_row[0].admin_row = True
1421 owner_row[0].admin_row = True
1422 else:
1422 else:
1423 usr = AttributeDict(usr.get_dict())
1423 usr = AttributeDict(usr.get_dict())
1424 usr.admin_row = True
1424 usr.admin_row = True
1425 usr.permission = _admin_perm
1425 usr.permission = _admin_perm
1426 super_admin_rows.append(usr)
1426 super_admin_rows.append(usr)
1427
1427
1428 return super_admin_rows + owner_row + perm_rows
1428 return super_admin_rows + owner_row + perm_rows
1429
1429
1430 def permission_user_groups(self):
1430 def permission_user_groups(self):
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1435
1435
1436 perm_rows = []
1436 perm_rows = []
1437 for _user_group in q.all():
1437 for _user_group in q.all():
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1439 usr.permission = _user_group.permission.permission_name
1439 usr.permission = _user_group.permission.permission_name
1440 perm_rows.append(usr)
1440 perm_rows.append(usr)
1441
1441
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1443 return perm_rows
1443 return perm_rows
1444
1444
1445 def _get_default_perms(self, user_group, suffix=''):
1445 def _get_default_perms(self, user_group, suffix=''):
1446 from rhodecode.model.permission import PermissionModel
1446 from rhodecode.model.permission import PermissionModel
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1448
1448
1449 def get_default_perms(self, suffix=''):
1449 def get_default_perms(self, suffix=''):
1450 return self._get_default_perms(self, suffix)
1450 return self._get_default_perms(self, suffix)
1451
1451
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1453 """
1453 """
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1455 basically forwarded.
1455 basically forwarded.
1456
1456
1457 """
1457 """
1458 user_group = self
1458 user_group = self
1459 data = {
1459 data = {
1460 'users_group_id': user_group.users_group_id,
1460 'users_group_id': user_group.users_group_id,
1461 'group_name': user_group.users_group_name,
1461 'group_name': user_group.users_group_name,
1462 'group_description': user_group.user_group_description,
1462 'group_description': user_group.user_group_description,
1463 'active': user_group.users_group_active,
1463 'active': user_group.users_group_active,
1464 'owner': user_group.user.username,
1464 'owner': user_group.user.username,
1465 'sync': user_group.sync,
1465 'sync': user_group.sync,
1466 'owner_email': user_group.user.email,
1466 'owner_email': user_group.user.email,
1467 }
1467 }
1468
1468
1469 if with_group_members:
1469 if with_group_members:
1470 users = []
1470 users = []
1471 for user in user_group.members:
1471 for user in user_group.members:
1472 user = user.user
1472 user = user.user
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1474 data['users'] = users
1474 data['users'] = users
1475
1475
1476 return data
1476 return data
1477
1477
1478
1478
1479 class UserGroupMember(Base, BaseModel):
1479 class UserGroupMember(Base, BaseModel):
1480 __tablename__ = 'users_groups_members'
1480 __tablename__ = 'users_groups_members'
1481 __table_args__ = (
1481 __table_args__ = (
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1484 )
1484 )
1485
1485
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1489
1489
1490 user = relationship('User', lazy='joined')
1490 user = relationship('User', lazy='joined')
1491 users_group = relationship('UserGroup')
1491 users_group = relationship('UserGroup')
1492
1492
1493 def __init__(self, gr_id='', u_id=''):
1493 def __init__(self, gr_id='', u_id=''):
1494 self.users_group_id = gr_id
1494 self.users_group_id = gr_id
1495 self.user_id = u_id
1495 self.user_id = u_id
1496
1496
1497
1497
1498 class RepositoryField(Base, BaseModel):
1498 class RepositoryField(Base, BaseModel):
1499 __tablename__ = 'repositories_fields'
1499 __tablename__ = 'repositories_fields'
1500 __table_args__ = (
1500 __table_args__ = (
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1504 )
1504 )
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1506
1506
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1509 field_key = Column("field_key", String(250))
1509 field_key = Column("field_key", String(250))
1510 field_label = Column("field_label", String(1024), nullable=False)
1510 field_label = Column("field_label", String(1024), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1515
1515
1516 repository = relationship('Repository')
1516 repository = relationship('Repository')
1517
1517
1518 @property
1518 @property
1519 def field_key_prefixed(self):
1519 def field_key_prefixed(self):
1520 return 'ex_%s' % self.field_key
1520 return 'ex_%s' % self.field_key
1521
1521
1522 @classmethod
1522 @classmethod
1523 def un_prefix_key(cls, key):
1523 def un_prefix_key(cls, key):
1524 if key.startswith(cls.PREFIX):
1524 if key.startswith(cls.PREFIX):
1525 return key[len(cls.PREFIX):]
1525 return key[len(cls.PREFIX):]
1526 return key
1526 return key
1527
1527
1528 @classmethod
1528 @classmethod
1529 def get_by_key_name(cls, key, repo):
1529 def get_by_key_name(cls, key, repo):
1530 row = cls.query()\
1530 row = cls.query()\
1531 .filter(cls.repository == repo)\
1531 .filter(cls.repository == repo)\
1532 .filter(cls.field_key == key).scalar()
1532 .filter(cls.field_key == key).scalar()
1533 return row
1533 return row
1534
1534
1535
1535
1536 class Repository(Base, BaseModel):
1536 class Repository(Base, BaseModel):
1537 __tablename__ = 'repositories'
1537 __tablename__ = 'repositories'
1538 __table_args__ = (
1538 __table_args__ = (
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1542 )
1542 )
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1546
1546
1547 STATE_CREATED = 'repo_state_created'
1547 STATE_CREATED = 'repo_state_created'
1548 STATE_PENDING = 'repo_state_pending'
1548 STATE_PENDING = 'repo_state_pending'
1549 STATE_ERROR = 'repo_state_error'
1549 STATE_ERROR = 'repo_state_error'
1550
1550
1551 LOCK_AUTOMATIC = 'lock_auto'
1551 LOCK_AUTOMATIC = 'lock_auto'
1552 LOCK_API = 'lock_api'
1552 LOCK_API = 'lock_api'
1553 LOCK_WEB = 'lock_web'
1553 LOCK_WEB = 'lock_web'
1554 LOCK_PULL = 'lock_pull'
1554 LOCK_PULL = 'lock_pull'
1555
1555
1556 NAME_SEP = URL_SEP
1556 NAME_SEP = URL_SEP
1557
1557
1558 repo_id = Column(
1558 repo_id = Column(
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1560 primary_key=True)
1560 primary_key=True)
1561 _repo_name = Column(
1561 _repo_name = Column(
1562 "repo_name", Text(), nullable=False, default=None)
1562 "repo_name", Text(), nullable=False, default=None)
1563 _repo_name_hash = Column(
1563 _repo_name_hash = Column(
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1566
1566
1567 clone_uri = Column(
1567 clone_uri = Column(
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1569 default=None)
1569 default=None)
1570 push_uri = Column(
1570 push_uri = Column(
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1572 default=None)
1572 default=None)
1573 repo_type = Column(
1573 repo_type = Column(
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1575 user_id = Column(
1575 user_id = Column(
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1577 unique=False, default=None)
1577 unique=False, default=None)
1578 private = Column(
1578 private = Column(
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1580 enable_statistics = Column(
1580 enable_statistics = Column(
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1582 enable_downloads = Column(
1582 enable_downloads = Column(
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1584 description = Column(
1584 description = Column(
1585 "description", String(10000), nullable=True, unique=None, default=None)
1585 "description", String(10000), nullable=True, unique=None, default=None)
1586 created_on = Column(
1586 created_on = Column(
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1588 default=datetime.datetime.now)
1588 default=datetime.datetime.now)
1589 updated_on = Column(
1589 updated_on = Column(
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1591 default=datetime.datetime.now)
1591 default=datetime.datetime.now)
1592 _landing_revision = Column(
1592 _landing_revision = Column(
1593 "landing_revision", String(255), nullable=False, unique=False,
1593 "landing_revision", String(255), nullable=False, unique=False,
1594 default=None)
1594 default=None)
1595 enable_locking = Column(
1595 enable_locking = Column(
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1597 default=False)
1597 default=False)
1598 _locked = Column(
1598 _locked = Column(
1599 "locked", String(255), nullable=True, unique=False, default=None)
1599 "locked", String(255), nullable=True, unique=False, default=None)
1600 _changeset_cache = Column(
1600 _changeset_cache = Column(
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1602
1602
1603 fork_id = Column(
1603 fork_id = Column(
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1605 nullable=True, unique=False, default=None)
1605 nullable=True, unique=False, default=None)
1606 group_id = Column(
1606 group_id = Column(
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1608 unique=False, default=None)
1608 unique=False, default=None)
1609
1609
1610 user = relationship('User', lazy='joined')
1610 user = relationship('User', lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1613 repo_to_perm = relationship(
1613 repo_to_perm = relationship(
1614 'UserRepoToPerm', cascade='all',
1614 'UserRepoToPerm', cascade='all',
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1618
1618
1619 followers = relationship(
1619 followers = relationship(
1620 'UserFollowing',
1620 'UserFollowing',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1622 cascade='all')
1622 cascade='all')
1623 extra_fields = relationship(
1623 extra_fields = relationship(
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1625 logs = relationship('UserLog')
1625 logs = relationship('UserLog')
1626 comments = relationship(
1626 comments = relationship(
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1628 pull_requests_source = relationship(
1628 pull_requests_source = relationship(
1629 'PullRequest',
1629 'PullRequest',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1631 cascade="all, delete, delete-orphan")
1631 cascade="all, delete, delete-orphan")
1632 pull_requests_target = relationship(
1632 pull_requests_target = relationship(
1633 'PullRequest',
1633 'PullRequest',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1635 cascade="all, delete, delete-orphan")
1635 cascade="all, delete, delete-orphan")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1638 integrations = relationship('Integration',
1638 integrations = relationship('Integration',
1639 cascade="all, delete, delete-orphan")
1639 cascade="all, delete, delete-orphan")
1640
1640
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1642
1642
1643 def __unicode__(self):
1643 def __unicode__(self):
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1645 safe_unicode(self.repo_name))
1645 safe_unicode(self.repo_name))
1646
1646
1647 @hybrid_property
1647 @hybrid_property
1648 def description_safe(self):
1648 def description_safe(self):
1649 from rhodecode.lib import helpers as h
1649 from rhodecode.lib import helpers as h
1650 return h.escape(self.description)
1650 return h.escape(self.description)
1651
1651
1652 @hybrid_property
1652 @hybrid_property
1653 def landing_rev(self):
1653 def landing_rev(self):
1654 # always should return [rev_type, rev]
1654 # always should return [rev_type, rev]
1655 if self._landing_revision:
1655 if self._landing_revision:
1656 _rev_info = self._landing_revision.split(':')
1656 _rev_info = self._landing_revision.split(':')
1657 if len(_rev_info) < 2:
1657 if len(_rev_info) < 2:
1658 _rev_info.insert(0, 'rev')
1658 _rev_info.insert(0, 'rev')
1659 return [_rev_info[0], _rev_info[1]]
1659 return [_rev_info[0], _rev_info[1]]
1660 return [None, None]
1660 return [None, None]
1661
1661
1662 @landing_rev.setter
1662 @landing_rev.setter
1663 def landing_rev(self, val):
1663 def landing_rev(self, val):
1664 if ':' not in val:
1664 if ':' not in val:
1665 raise ValueError('value must be delimited with `:` and consist '
1665 raise ValueError('value must be delimited with `:` and consist '
1666 'of <rev_type>:<rev>, got %s instead' % val)
1666 'of <rev_type>:<rev>, got %s instead' % val)
1667 self._landing_revision = val
1667 self._landing_revision = val
1668
1668
1669 @hybrid_property
1669 @hybrid_property
1670 def locked(self):
1670 def locked(self):
1671 if self._locked:
1671 if self._locked:
1672 user_id, timelocked, reason = self._locked.split(':')
1672 user_id, timelocked, reason = self._locked.split(':')
1673 lock_values = int(user_id), timelocked, reason
1673 lock_values = int(user_id), timelocked, reason
1674 else:
1674 else:
1675 lock_values = [None, None, None]
1675 lock_values = [None, None, None]
1676 return lock_values
1676 return lock_values
1677
1677
1678 @locked.setter
1678 @locked.setter
1679 def locked(self, val):
1679 def locked(self, val):
1680 if val and isinstance(val, (list, tuple)):
1680 if val and isinstance(val, (list, tuple)):
1681 self._locked = ':'.join(map(str, val))
1681 self._locked = ':'.join(map(str, val))
1682 else:
1682 else:
1683 self._locked = None
1683 self._locked = None
1684
1684
1685 @hybrid_property
1685 @hybrid_property
1686 def changeset_cache(self):
1686 def changeset_cache(self):
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1688 dummy = EmptyCommit().__json__()
1688 dummy = EmptyCommit().__json__()
1689 if not self._changeset_cache:
1689 if not self._changeset_cache:
1690 return dummy
1690 return dummy
1691 try:
1691 try:
1692 return json.loads(self._changeset_cache)
1692 return json.loads(self._changeset_cache)
1693 except TypeError:
1693 except TypeError:
1694 return dummy
1694 return dummy
1695 except Exception:
1695 except Exception:
1696 log.error(traceback.format_exc())
1696 log.error(traceback.format_exc())
1697 return dummy
1697 return dummy
1698
1698
1699 @changeset_cache.setter
1699 @changeset_cache.setter
1700 def changeset_cache(self, val):
1700 def changeset_cache(self, val):
1701 try:
1701 try:
1702 self._changeset_cache = json.dumps(val)
1702 self._changeset_cache = json.dumps(val)
1703 except Exception:
1703 except Exception:
1704 log.error(traceback.format_exc())
1704 log.error(traceback.format_exc())
1705
1705
1706 @hybrid_property
1706 @hybrid_property
1707 def repo_name(self):
1707 def repo_name(self):
1708 return self._repo_name
1708 return self._repo_name
1709
1709
1710 @repo_name.setter
1710 @repo_name.setter
1711 def repo_name(self, value):
1711 def repo_name(self, value):
1712 self._repo_name = value
1712 self._repo_name = value
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1714
1714
1715 @classmethod
1715 @classmethod
1716 def normalize_repo_name(cls, repo_name):
1716 def normalize_repo_name(cls, repo_name):
1717 """
1717 """
1718 Normalizes os specific repo_name to the format internally stored inside
1718 Normalizes os specific repo_name to the format internally stored inside
1719 database using URL_SEP
1719 database using URL_SEP
1720
1720
1721 :param cls:
1721 :param cls:
1722 :param repo_name:
1722 :param repo_name:
1723 """
1723 """
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1725
1725
1726 @classmethod
1726 @classmethod
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1728 session = Session()
1728 session = Session()
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1730
1730
1731 if cache:
1731 if cache:
1732 if identity_cache:
1732 if identity_cache:
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1734 if val:
1734 if val:
1735 return val
1735 return val
1736 else:
1736 else:
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1738 q = q.options(
1738 q = q.options(
1739 FromCache("sql_cache_short", cache_key))
1739 FromCache("sql_cache_short", cache_key))
1740
1740
1741 return q.scalar()
1741 return q.scalar()
1742
1742
1743 @classmethod
1743 @classmethod
1744 def get_by_id_or_repo_name(cls, repoid):
1744 def get_by_id_or_repo_name(cls, repoid):
1745 if isinstance(repoid, (int, long)):
1745 if isinstance(repoid, (int, long)):
1746 try:
1746 try:
1747 repo = cls.get(repoid)
1747 repo = cls.get(repoid)
1748 except ValueError:
1748 except ValueError:
1749 repo = None
1749 repo = None
1750 else:
1750 else:
1751 repo = cls.get_by_repo_name(repoid)
1751 repo = cls.get_by_repo_name(repoid)
1752 return repo
1752 return repo
1753
1753
1754 @classmethod
1754 @classmethod
1755 def get_by_full_path(cls, repo_full_path):
1755 def get_by_full_path(cls, repo_full_path):
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1757 repo_name = cls.normalize_repo_name(repo_name)
1757 repo_name = cls.normalize_repo_name(repo_name)
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1759
1759
1760 @classmethod
1760 @classmethod
1761 def get_repo_forks(cls, repo_id):
1761 def get_repo_forks(cls, repo_id):
1762 return cls.query().filter(Repository.fork_id == repo_id)
1762 return cls.query().filter(Repository.fork_id == repo_id)
1763
1763
1764 @classmethod
1764 @classmethod
1765 def base_path(cls):
1765 def base_path(cls):
1766 """
1766 """
1767 Returns base path when all repos are stored
1767 Returns base path when all repos are stored
1768
1768
1769 :param cls:
1769 :param cls:
1770 """
1770 """
1771 q = Session().query(RhodeCodeUi)\
1771 q = Session().query(RhodeCodeUi)\
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1774 return q.one().ui_value
1774 return q.one().ui_value
1775
1775
1776 @classmethod
1776 @classmethod
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1778 case_insensitive=True):
1778 case_insensitive=True):
1779 q = Repository.query()
1779 q = Repository.query()
1780
1780
1781 if not isinstance(user_id, Optional):
1781 if not isinstance(user_id, Optional):
1782 q = q.filter(Repository.user_id == user_id)
1782 q = q.filter(Repository.user_id == user_id)
1783
1783
1784 if not isinstance(group_id, Optional):
1784 if not isinstance(group_id, Optional):
1785 q = q.filter(Repository.group_id == group_id)
1785 q = q.filter(Repository.group_id == group_id)
1786
1786
1787 if case_insensitive:
1787 if case_insensitive:
1788 q = q.order_by(func.lower(Repository.repo_name))
1788 q = q.order_by(func.lower(Repository.repo_name))
1789 else:
1789 else:
1790 q = q.order_by(Repository.repo_name)
1790 q = q.order_by(Repository.repo_name)
1791 return q.all()
1791 return q.all()
1792
1792
1793 @property
1793 @property
1794 def forks(self):
1794 def forks(self):
1795 """
1795 """
1796 Return forks of this repo
1796 Return forks of this repo
1797 """
1797 """
1798 return Repository.get_repo_forks(self.repo_id)
1798 return Repository.get_repo_forks(self.repo_id)
1799
1799
1800 @property
1800 @property
1801 def parent(self):
1801 def parent(self):
1802 """
1802 """
1803 Returns fork parent
1803 Returns fork parent
1804 """
1804 """
1805 return self.fork
1805 return self.fork
1806
1806
1807 @property
1807 @property
1808 def just_name(self):
1808 def just_name(self):
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1810
1810
1811 @property
1811 @property
1812 def groups_with_parents(self):
1812 def groups_with_parents(self):
1813 groups = []
1813 groups = []
1814 if self.group is None:
1814 if self.group is None:
1815 return groups
1815 return groups
1816
1816
1817 cur_gr = self.group
1817 cur_gr = self.group
1818 groups.insert(0, cur_gr)
1818 groups.insert(0, cur_gr)
1819 while 1:
1819 while 1:
1820 gr = getattr(cur_gr, 'parent_group', None)
1820 gr = getattr(cur_gr, 'parent_group', None)
1821 cur_gr = cur_gr.parent_group
1821 cur_gr = cur_gr.parent_group
1822 if gr is None:
1822 if gr is None:
1823 break
1823 break
1824 groups.insert(0, gr)
1824 groups.insert(0, gr)
1825
1825
1826 return groups
1826 return groups
1827
1827
1828 @property
1828 @property
1829 def groups_and_repo(self):
1829 def groups_and_repo(self):
1830 return self.groups_with_parents, self
1830 return self.groups_with_parents, self
1831
1831
1832 @LazyProperty
1832 @LazyProperty
1833 def repo_path(self):
1833 def repo_path(self):
1834 """
1834 """
1835 Returns base full path for that repository means where it actually
1835 Returns base full path for that repository means where it actually
1836 exists on a filesystem
1836 exists on a filesystem
1837 """
1837 """
1838 q = Session().query(RhodeCodeUi).filter(
1838 q = Session().query(RhodeCodeUi).filter(
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1841 return q.one().ui_value
1841 return q.one().ui_value
1842
1842
1843 @property
1843 @property
1844 def repo_full_path(self):
1844 def repo_full_path(self):
1845 p = [self.repo_path]
1845 p = [self.repo_path]
1846 # we need to split the name by / since this is how we store the
1846 # we need to split the name by / since this is how we store the
1847 # names in the database, but that eventually needs to be converted
1847 # names in the database, but that eventually needs to be converted
1848 # into a valid system path
1848 # into a valid system path
1849 p += self.repo_name.split(self.NAME_SEP)
1849 p += self.repo_name.split(self.NAME_SEP)
1850 return os.path.join(*map(safe_unicode, p))
1850 return os.path.join(*map(safe_unicode, p))
1851
1851
1852 @property
1852 @property
1853 def cache_keys(self):
1853 def cache_keys(self):
1854 """
1854 """
1855 Returns associated cache keys for that repo
1855 Returns associated cache keys for that repo
1856 """
1856 """
1857 return CacheKey.query()\
1857 return CacheKey.query()\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1859 .order_by(CacheKey.cache_key)\
1859 .order_by(CacheKey.cache_key)\
1860 .all()
1860 .all()
1861
1861
1862 @property
1862 @property
1863 def cached_diffs_relative_dir(self):
1863 def cached_diffs_relative_dir(self):
1864 """
1864 """
1865 Return a relative to the repository store path of cached diffs
1865 Return a relative to the repository store path of cached diffs
1866 used for safe display for users, who shouldn't know the absolute store
1866 used for safe display for users, who shouldn't know the absolute store
1867 path
1867 path
1868 """
1868 """
1869 return os.path.join(
1869 return os.path.join(
1870 os.path.dirname(self.repo_name),
1870 os.path.dirname(self.repo_name),
1871 self.cached_diffs_dir.split(os.path.sep)[-1])
1871 self.cached_diffs_dir.split(os.path.sep)[-1])
1872
1872
1873 @property
1873 @property
1874 def cached_diffs_dir(self):
1874 def cached_diffs_dir(self):
1875 path = self.repo_full_path
1875 path = self.repo_full_path
1876 return os.path.join(
1876 return os.path.join(
1877 os.path.dirname(path),
1877 os.path.dirname(path),
1878 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1878 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1879
1879
1880 def cached_diffs(self):
1880 def cached_diffs(self):
1881 diff_cache_dir = self.cached_diffs_dir
1881 diff_cache_dir = self.cached_diffs_dir
1882 if os.path.isdir(diff_cache_dir):
1882 if os.path.isdir(diff_cache_dir):
1883 return os.listdir(diff_cache_dir)
1883 return os.listdir(diff_cache_dir)
1884 return []
1884 return []
1885
1885
1886 def get_new_name(self, repo_name):
1886 def get_new_name(self, repo_name):
1887 """
1887 """
1888 returns new full repository name based on assigned group and new new
1888 returns new full repository name based on assigned group and new new
1889
1889
1890 :param group_name:
1890 :param group_name:
1891 """
1891 """
1892 path_prefix = self.group.full_path_splitted if self.group else []
1892 path_prefix = self.group.full_path_splitted if self.group else []
1893 return self.NAME_SEP.join(path_prefix + [repo_name])
1893 return self.NAME_SEP.join(path_prefix + [repo_name])
1894
1894
1895 @property
1895 @property
1896 def _config(self):
1896 def _config(self):
1897 """
1897 """
1898 Returns db based config object.
1898 Returns db based config object.
1899 """
1899 """
1900 from rhodecode.lib.utils import make_db_config
1900 from rhodecode.lib.utils import make_db_config
1901 return make_db_config(clear_session=False, repo=self)
1901 return make_db_config(clear_session=False, repo=self)
1902
1902
1903 def permissions(self, with_admins=True, with_owner=True):
1903 def permissions(self, with_admins=True, with_owner=True):
1904 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1904 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1905 q = q.options(joinedload(UserRepoToPerm.repository),
1905 q = q.options(joinedload(UserRepoToPerm.repository),
1906 joinedload(UserRepoToPerm.user),
1906 joinedload(UserRepoToPerm.user),
1907 joinedload(UserRepoToPerm.permission),)
1907 joinedload(UserRepoToPerm.permission),)
1908
1908
1909 # get owners and admins and permissions. We do a trick of re-writing
1909 # get owners and admins and permissions. We do a trick of re-writing
1910 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1910 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1911 # has a global reference and changing one object propagates to all
1911 # has a global reference and changing one object propagates to all
1912 # others. This means if admin is also an owner admin_row that change
1912 # others. This means if admin is also an owner admin_row that change
1913 # would propagate to both objects
1913 # would propagate to both objects
1914 perm_rows = []
1914 perm_rows = []
1915 for _usr in q.all():
1915 for _usr in q.all():
1916 usr = AttributeDict(_usr.user.get_dict())
1916 usr = AttributeDict(_usr.user.get_dict())
1917 usr.permission = _usr.permission.permission_name
1917 usr.permission = _usr.permission.permission_name
1918 usr.permission_id = _usr.repo_to_perm_id
1918 usr.permission_id = _usr.repo_to_perm_id
1919 perm_rows.append(usr)
1919 perm_rows.append(usr)
1920
1920
1921 # filter the perm rows by 'default' first and then sort them by
1921 # filter the perm rows by 'default' first and then sort them by
1922 # admin,write,read,none permissions sorted again alphabetically in
1922 # admin,write,read,none permissions sorted again alphabetically in
1923 # each group
1923 # each group
1924 perm_rows = sorted(perm_rows, key=display_user_sort)
1924 perm_rows = sorted(perm_rows, key=display_user_sort)
1925
1925
1926 _admin_perm = 'repository.admin'
1926 _admin_perm = 'repository.admin'
1927 owner_row = []
1927 owner_row = []
1928 if with_owner:
1928 if with_owner:
1929 usr = AttributeDict(self.user.get_dict())
1929 usr = AttributeDict(self.user.get_dict())
1930 usr.owner_row = True
1930 usr.owner_row = True
1931 usr.permission = _admin_perm
1931 usr.permission = _admin_perm
1932 usr.permission_id = None
1932 usr.permission_id = None
1933 owner_row.append(usr)
1933 owner_row.append(usr)
1934
1934
1935 super_admin_rows = []
1935 super_admin_rows = []
1936 if with_admins:
1936 if with_admins:
1937 for usr in User.get_all_super_admins():
1937 for usr in User.get_all_super_admins():
1938 # if this admin is also owner, don't double the record
1938 # if this admin is also owner, don't double the record
1939 if usr.user_id == owner_row[0].user_id:
1939 if usr.user_id == owner_row[0].user_id:
1940 owner_row[0].admin_row = True
1940 owner_row[0].admin_row = True
1941 else:
1941 else:
1942 usr = AttributeDict(usr.get_dict())
1942 usr = AttributeDict(usr.get_dict())
1943 usr.admin_row = True
1943 usr.admin_row = True
1944 usr.permission = _admin_perm
1944 usr.permission = _admin_perm
1945 usr.permission_id = None
1945 usr.permission_id = None
1946 super_admin_rows.append(usr)
1946 super_admin_rows.append(usr)
1947
1947
1948 return super_admin_rows + owner_row + perm_rows
1948 return super_admin_rows + owner_row + perm_rows
1949
1949
1950 def permission_user_groups(self):
1950 def permission_user_groups(self):
1951 q = UserGroupRepoToPerm.query().filter(
1951 q = UserGroupRepoToPerm.query().filter(
1952 UserGroupRepoToPerm.repository == self)
1952 UserGroupRepoToPerm.repository == self)
1953 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1953 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1954 joinedload(UserGroupRepoToPerm.users_group),
1954 joinedload(UserGroupRepoToPerm.users_group),
1955 joinedload(UserGroupRepoToPerm.permission),)
1955 joinedload(UserGroupRepoToPerm.permission),)
1956
1956
1957 perm_rows = []
1957 perm_rows = []
1958 for _user_group in q.all():
1958 for _user_group in q.all():
1959 usr = AttributeDict(_user_group.users_group.get_dict())
1959 usr = AttributeDict(_user_group.users_group.get_dict())
1960 usr.permission = _user_group.permission.permission_name
1960 usr.permission = _user_group.permission.permission_name
1961 perm_rows.append(usr)
1961 perm_rows.append(usr)
1962
1962
1963 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1963 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1964 return perm_rows
1964 return perm_rows
1965
1965
1966 def get_api_data(self, include_secrets=False):
1966 def get_api_data(self, include_secrets=False):
1967 """
1967 """
1968 Common function for generating repo api data
1968 Common function for generating repo api data
1969
1969
1970 :param include_secrets: See :meth:`User.get_api_data`.
1970 :param include_secrets: See :meth:`User.get_api_data`.
1971
1971
1972 """
1972 """
1973 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1973 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1974 # move this methods on models level.
1974 # move this methods on models level.
1975 from rhodecode.model.settings import SettingsModel
1975 from rhodecode.model.settings import SettingsModel
1976 from rhodecode.model.repo import RepoModel
1976 from rhodecode.model.repo import RepoModel
1977
1977
1978 repo = self
1978 repo = self
1979 _user_id, _time, _reason = self.locked
1979 _user_id, _time, _reason = self.locked
1980
1980
1981 data = {
1981 data = {
1982 'repo_id': repo.repo_id,
1982 'repo_id': repo.repo_id,
1983 'repo_name': repo.repo_name,
1983 'repo_name': repo.repo_name,
1984 'repo_type': repo.repo_type,
1984 'repo_type': repo.repo_type,
1985 'clone_uri': repo.clone_uri or '',
1985 'clone_uri': repo.clone_uri or '',
1986 'push_uri': repo.push_uri or '',
1986 'push_uri': repo.push_uri or '',
1987 'url': RepoModel().get_url(self),
1987 'url': RepoModel().get_url(self),
1988 'private': repo.private,
1988 'private': repo.private,
1989 'created_on': repo.created_on,
1989 'created_on': repo.created_on,
1990 'description': repo.description_safe,
1990 'description': repo.description_safe,
1991 'landing_rev': repo.landing_rev,
1991 'landing_rev': repo.landing_rev,
1992 'owner': repo.user.username,
1992 'owner': repo.user.username,
1993 'fork_of': repo.fork.repo_name if repo.fork else None,
1993 'fork_of': repo.fork.repo_name if repo.fork else None,
1994 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1994 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1995 'enable_statistics': repo.enable_statistics,
1995 'enable_statistics': repo.enable_statistics,
1996 'enable_locking': repo.enable_locking,
1996 'enable_locking': repo.enable_locking,
1997 'enable_downloads': repo.enable_downloads,
1997 'enable_downloads': repo.enable_downloads,
1998 'last_changeset': repo.changeset_cache,
1998 'last_changeset': repo.changeset_cache,
1999 'locked_by': User.get(_user_id).get_api_data(
1999 'locked_by': User.get(_user_id).get_api_data(
2000 include_secrets=include_secrets) if _user_id else None,
2000 include_secrets=include_secrets) if _user_id else None,
2001 'locked_date': time_to_datetime(_time) if _time else None,
2001 'locked_date': time_to_datetime(_time) if _time else None,
2002 'lock_reason': _reason if _reason else None,
2002 'lock_reason': _reason if _reason else None,
2003 }
2003 }
2004
2004
2005 # TODO: mikhail: should be per-repo settings here
2005 # TODO: mikhail: should be per-repo settings here
2006 rc_config = SettingsModel().get_all_settings()
2006 rc_config = SettingsModel().get_all_settings()
2007 repository_fields = str2bool(
2007 repository_fields = str2bool(
2008 rc_config.get('rhodecode_repository_fields'))
2008 rc_config.get('rhodecode_repository_fields'))
2009 if repository_fields:
2009 if repository_fields:
2010 for f in self.extra_fields:
2010 for f in self.extra_fields:
2011 data[f.field_key_prefixed] = f.field_value
2011 data[f.field_key_prefixed] = f.field_value
2012
2012
2013 return data
2013 return data
2014
2014
2015 @classmethod
2015 @classmethod
2016 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2016 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2017 if not lock_time:
2017 if not lock_time:
2018 lock_time = time.time()
2018 lock_time = time.time()
2019 if not lock_reason:
2019 if not lock_reason:
2020 lock_reason = cls.LOCK_AUTOMATIC
2020 lock_reason = cls.LOCK_AUTOMATIC
2021 repo.locked = [user_id, lock_time, lock_reason]
2021 repo.locked = [user_id, lock_time, lock_reason]
2022 Session().add(repo)
2022 Session().add(repo)
2023 Session().commit()
2023 Session().commit()
2024
2024
2025 @classmethod
2025 @classmethod
2026 def unlock(cls, repo):
2026 def unlock(cls, repo):
2027 repo.locked = None
2027 repo.locked = None
2028 Session().add(repo)
2028 Session().add(repo)
2029 Session().commit()
2029 Session().commit()
2030
2030
2031 @classmethod
2031 @classmethod
2032 def getlock(cls, repo):
2032 def getlock(cls, repo):
2033 return repo.locked
2033 return repo.locked
2034
2034
2035 def is_user_lock(self, user_id):
2035 def is_user_lock(self, user_id):
2036 if self.lock[0]:
2036 if self.lock[0]:
2037 lock_user_id = safe_int(self.lock[0])
2037 lock_user_id = safe_int(self.lock[0])
2038 user_id = safe_int(user_id)
2038 user_id = safe_int(user_id)
2039 # both are ints, and they are equal
2039 # both are ints, and they are equal
2040 return all([lock_user_id, user_id]) and lock_user_id == user_id
2040 return all([lock_user_id, user_id]) and lock_user_id == user_id
2041
2041
2042 return False
2042 return False
2043
2043
2044 def get_locking_state(self, action, user_id, only_when_enabled=True):
2044 def get_locking_state(self, action, user_id, only_when_enabled=True):
2045 """
2045 """
2046 Checks locking on this repository, if locking is enabled and lock is
2046 Checks locking on this repository, if locking is enabled and lock is
2047 present returns a tuple of make_lock, locked, locked_by.
2047 present returns a tuple of make_lock, locked, locked_by.
2048 make_lock can have 3 states None (do nothing) True, make lock
2048 make_lock can have 3 states None (do nothing) True, make lock
2049 False release lock, This value is later propagated to hooks, which
2049 False release lock, This value is later propagated to hooks, which
2050 do the locking. Think about this as signals passed to hooks what to do.
2050 do the locking. Think about this as signals passed to hooks what to do.
2051
2051
2052 """
2052 """
2053 # TODO: johbo: This is part of the business logic and should be moved
2053 # TODO: johbo: This is part of the business logic and should be moved
2054 # into the RepositoryModel.
2054 # into the RepositoryModel.
2055
2055
2056 if action not in ('push', 'pull'):
2056 if action not in ('push', 'pull'):
2057 raise ValueError("Invalid action value: %s" % repr(action))
2057 raise ValueError("Invalid action value: %s" % repr(action))
2058
2058
2059 # defines if locked error should be thrown to user
2059 # defines if locked error should be thrown to user
2060 currently_locked = False
2060 currently_locked = False
2061 # defines if new lock should be made, tri-state
2061 # defines if new lock should be made, tri-state
2062 make_lock = None
2062 make_lock = None
2063 repo = self
2063 repo = self
2064 user = User.get(user_id)
2064 user = User.get(user_id)
2065
2065
2066 lock_info = repo.locked
2066 lock_info = repo.locked
2067
2067
2068 if repo and (repo.enable_locking or not only_when_enabled):
2068 if repo and (repo.enable_locking or not only_when_enabled):
2069 if action == 'push':
2069 if action == 'push':
2070 # check if it's already locked !, if it is compare users
2070 # check if it's already locked !, if it is compare users
2071 locked_by_user_id = lock_info[0]
2071 locked_by_user_id = lock_info[0]
2072 if user.user_id == locked_by_user_id:
2072 if user.user_id == locked_by_user_id:
2073 log.debug(
2073 log.debug(
2074 'Got `push` action from user %s, now unlocking', user)
2074 'Got `push` action from user %s, now unlocking', user)
2075 # unlock if we have push from user who locked
2075 # unlock if we have push from user who locked
2076 make_lock = False
2076 make_lock = False
2077 else:
2077 else:
2078 # we're not the same user who locked, ban with
2078 # we're not the same user who locked, ban with
2079 # code defined in settings (default is 423 HTTP Locked) !
2079 # code defined in settings (default is 423 HTTP Locked) !
2080 log.debug('Repo %s is currently locked by %s', repo, user)
2080 log.debug('Repo %s is currently locked by %s', repo, user)
2081 currently_locked = True
2081 currently_locked = True
2082 elif action == 'pull':
2082 elif action == 'pull':
2083 # [0] user [1] date
2083 # [0] user [1] date
2084 if lock_info[0] and lock_info[1]:
2084 if lock_info[0] and lock_info[1]:
2085 log.debug('Repo %s is currently locked by %s', repo, user)
2085 log.debug('Repo %s is currently locked by %s', repo, user)
2086 currently_locked = True
2086 currently_locked = True
2087 else:
2087 else:
2088 log.debug('Setting lock on repo %s by %s', repo, user)
2088 log.debug('Setting lock on repo %s by %s', repo, user)
2089 make_lock = True
2089 make_lock = True
2090
2090
2091 else:
2091 else:
2092 log.debug('Repository %s do not have locking enabled', repo)
2092 log.debug('Repository %s do not have locking enabled', repo)
2093
2093
2094 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2094 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2095 make_lock, currently_locked, lock_info)
2095 make_lock, currently_locked, lock_info)
2096
2096
2097 from rhodecode.lib.auth import HasRepoPermissionAny
2097 from rhodecode.lib.auth import HasRepoPermissionAny
2098 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2098 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2099 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2099 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2100 # if we don't have at least write permission we cannot make a lock
2100 # if we don't have at least write permission we cannot make a lock
2101 log.debug('lock state reset back to FALSE due to lack '
2101 log.debug('lock state reset back to FALSE due to lack '
2102 'of at least read permission')
2102 'of at least read permission')
2103 make_lock = False
2103 make_lock = False
2104
2104
2105 return make_lock, currently_locked, lock_info
2105 return make_lock, currently_locked, lock_info
2106
2106
2107 @property
2107 @property
2108 def last_db_change(self):
2108 def last_db_change(self):
2109 return self.updated_on
2109 return self.updated_on
2110
2110
2111 @property
2111 @property
2112 def clone_uri_hidden(self):
2112 def clone_uri_hidden(self):
2113 clone_uri = self.clone_uri
2113 clone_uri = self.clone_uri
2114 if clone_uri:
2114 if clone_uri:
2115 import urlobject
2115 import urlobject
2116 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2116 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2117 if url_obj.password:
2117 if url_obj.password:
2118 clone_uri = url_obj.with_password('*****')
2118 clone_uri = url_obj.with_password('*****')
2119 return clone_uri
2119 return clone_uri
2120
2120
2121 @property
2121 @property
2122 def push_uri_hidden(self):
2122 def push_uri_hidden(self):
2123 push_uri = self.push_uri
2123 push_uri = self.push_uri
2124 if push_uri:
2124 if push_uri:
2125 import urlobject
2125 import urlobject
2126 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2126 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2127 if url_obj.password:
2127 if url_obj.password:
2128 push_uri = url_obj.with_password('*****')
2128 push_uri = url_obj.with_password('*****')
2129 return push_uri
2129 return push_uri
2130
2130
2131 def clone_url(self, **override):
2131 def clone_url(self, **override):
2132 from rhodecode.model.settings import SettingsModel
2132 from rhodecode.model.settings import SettingsModel
2133
2133
2134 uri_tmpl = None
2134 uri_tmpl = None
2135 if 'with_id' in override:
2135 if 'with_id' in override:
2136 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2136 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2137 del override['with_id']
2137 del override['with_id']
2138
2138
2139 if 'uri_tmpl' in override:
2139 if 'uri_tmpl' in override:
2140 uri_tmpl = override['uri_tmpl']
2140 uri_tmpl = override['uri_tmpl']
2141 del override['uri_tmpl']
2141 del override['uri_tmpl']
2142
2142
2143 ssh = False
2143 ssh = False
2144 if 'ssh' in override:
2144 if 'ssh' in override:
2145 ssh = True
2145 ssh = True
2146 del override['ssh']
2146 del override['ssh']
2147
2147
2148 # we didn't override our tmpl from **overrides
2148 # we didn't override our tmpl from **overrides
2149 if not uri_tmpl:
2149 if not uri_tmpl:
2150 rc_config = SettingsModel().get_all_settings(cache=True)
2150 rc_config = SettingsModel().get_all_settings(cache=True)
2151 if ssh:
2151 if ssh:
2152 uri_tmpl = rc_config.get(
2152 uri_tmpl = rc_config.get(
2153 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2153 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2154 else:
2154 else:
2155 uri_tmpl = rc_config.get(
2155 uri_tmpl = rc_config.get(
2156 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2156 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2157
2157
2158 request = get_current_request()
2158 request = get_current_request()
2159 return get_clone_url(request=request,
2159 return get_clone_url(request=request,
2160 uri_tmpl=uri_tmpl,
2160 uri_tmpl=uri_tmpl,
2161 repo_name=self.repo_name,
2161 repo_name=self.repo_name,
2162 repo_id=self.repo_id, **override)
2162 repo_id=self.repo_id, **override)
2163
2163
2164 def set_state(self, state):
2164 def set_state(self, state):
2165 self.repo_state = state
2165 self.repo_state = state
2166 Session().add(self)
2166 Session().add(self)
2167 #==========================================================================
2167 #==========================================================================
2168 # SCM PROPERTIES
2168 # SCM PROPERTIES
2169 #==========================================================================
2169 #==========================================================================
2170
2170
2171 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2171 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2172 return get_commit_safe(
2172 return get_commit_safe(
2173 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2173 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2174
2174
2175 def get_changeset(self, rev=None, pre_load=None):
2175 def get_changeset(self, rev=None, pre_load=None):
2176 warnings.warn("Use get_commit", DeprecationWarning)
2176 warnings.warn("Use get_commit", DeprecationWarning)
2177 commit_id = None
2177 commit_id = None
2178 commit_idx = None
2178 commit_idx = None
2179 if isinstance(rev, basestring):
2179 if isinstance(rev, basestring):
2180 commit_id = rev
2180 commit_id = rev
2181 else:
2181 else:
2182 commit_idx = rev
2182 commit_idx = rev
2183 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2183 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2184 pre_load=pre_load)
2184 pre_load=pre_load)
2185
2185
2186 def get_landing_commit(self):
2186 def get_landing_commit(self):
2187 """
2187 """
2188 Returns landing commit, or if that doesn't exist returns the tip
2188 Returns landing commit, or if that doesn't exist returns the tip
2189 """
2189 """
2190 _rev_type, _rev = self.landing_rev
2190 _rev_type, _rev = self.landing_rev
2191 commit = self.get_commit(_rev)
2191 commit = self.get_commit(_rev)
2192 if isinstance(commit, EmptyCommit):
2192 if isinstance(commit, EmptyCommit):
2193 return self.get_commit()
2193 return self.get_commit()
2194 return commit
2194 return commit
2195
2195
2196 def update_commit_cache(self, cs_cache=None, config=None):
2196 def update_commit_cache(self, cs_cache=None, config=None):
2197 """
2197 """
2198 Update cache of last changeset for repository, keys should be::
2198 Update cache of last changeset for repository, keys should be::
2199
2199
2200 short_id
2200 short_id
2201 raw_id
2201 raw_id
2202 revision
2202 revision
2203 parents
2203 parents
2204 message
2204 message
2205 date
2205 date
2206 author
2206 author
2207
2207
2208 :param cs_cache:
2208 :param cs_cache:
2209 """
2209 """
2210 from rhodecode.lib.vcs.backends.base import BaseChangeset
2210 from rhodecode.lib.vcs.backends.base import BaseChangeset
2211 if cs_cache is None:
2211 if cs_cache is None:
2212 # use no-cache version here
2212 # use no-cache version here
2213 scm_repo = self.scm_instance(cache=False, config=config)
2213 scm_repo = self.scm_instance(cache=False, config=config)
2214 if scm_repo:
2214 if scm_repo:
2215 cs_cache = scm_repo.get_commit(
2215 cs_cache = scm_repo.get_commit(
2216 pre_load=["author", "date", "message", "parents"])
2216 pre_load=["author", "date", "message", "parents"])
2217 else:
2217 else:
2218 cs_cache = EmptyCommit()
2218 cs_cache = EmptyCommit()
2219
2219
2220 if isinstance(cs_cache, BaseChangeset):
2220 if isinstance(cs_cache, BaseChangeset):
2221 cs_cache = cs_cache.__json__()
2221 cs_cache = cs_cache.__json__()
2222
2222
2223 def is_outdated(new_cs_cache):
2223 def is_outdated(new_cs_cache):
2224 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2224 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2225 new_cs_cache['revision'] != self.changeset_cache['revision']):
2225 new_cs_cache['revision'] != self.changeset_cache['revision']):
2226 return True
2226 return True
2227 return False
2227 return False
2228
2228
2229 # check if we have maybe already latest cached revision
2229 # check if we have maybe already latest cached revision
2230 if is_outdated(cs_cache) or not self.changeset_cache:
2230 if is_outdated(cs_cache) or not self.changeset_cache:
2231 _default = datetime.datetime.fromtimestamp(0)
2231 _default = datetime.datetime.fromtimestamp(0)
2232 last_change = cs_cache.get('date') or _default
2232 last_change = cs_cache.get('date') or _default
2233 log.debug('updated repo %s with new cs cache %s',
2233 log.debug('updated repo %s with new cs cache %s',
2234 self.repo_name, cs_cache)
2234 self.repo_name, cs_cache)
2235 self.updated_on = last_change
2235 self.updated_on = last_change
2236 self.changeset_cache = cs_cache
2236 self.changeset_cache = cs_cache
2237 Session().add(self)
2237 Session().add(self)
2238 Session().commit()
2238 Session().commit()
2239 else:
2239 else:
2240 log.debug('Skipping update_commit_cache for repo:`%s` '
2240 log.debug('Skipping update_commit_cache for repo:`%s` '
2241 'commit already with latest changes', self.repo_name)
2241 'commit already with latest changes', self.repo_name)
2242
2242
2243 @property
2243 @property
2244 def tip(self):
2244 def tip(self):
2245 return self.get_commit('tip')
2245 return self.get_commit('tip')
2246
2246
2247 @property
2247 @property
2248 def author(self):
2248 def author(self):
2249 return self.tip.author
2249 return self.tip.author
2250
2250
2251 @property
2251 @property
2252 def last_change(self):
2252 def last_change(self):
2253 return self.scm_instance().last_change
2253 return self.scm_instance().last_change
2254
2254
2255 def get_comments(self, revisions=None):
2255 def get_comments(self, revisions=None):
2256 """
2256 """
2257 Returns comments for this repository grouped by revisions
2257 Returns comments for this repository grouped by revisions
2258
2258
2259 :param revisions: filter query by revisions only
2259 :param revisions: filter query by revisions only
2260 """
2260 """
2261 cmts = ChangesetComment.query()\
2261 cmts = ChangesetComment.query()\
2262 .filter(ChangesetComment.repo == self)
2262 .filter(ChangesetComment.repo == self)
2263 if revisions:
2263 if revisions:
2264 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2264 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2265 grouped = collections.defaultdict(list)
2265 grouped = collections.defaultdict(list)
2266 for cmt in cmts.all():
2266 for cmt in cmts.all():
2267 grouped[cmt.revision].append(cmt)
2267 grouped[cmt.revision].append(cmt)
2268 return grouped
2268 return grouped
2269
2269
2270 def statuses(self, revisions=None):
2270 def statuses(self, revisions=None):
2271 """
2271 """
2272 Returns statuses for this repository
2272 Returns statuses for this repository
2273
2273
2274 :param revisions: list of revisions to get statuses for
2274 :param revisions: list of revisions to get statuses for
2275 """
2275 """
2276 statuses = ChangesetStatus.query()\
2276 statuses = ChangesetStatus.query()\
2277 .filter(ChangesetStatus.repo == self)\
2277 .filter(ChangesetStatus.repo == self)\
2278 .filter(ChangesetStatus.version == 0)
2278 .filter(ChangesetStatus.version == 0)
2279
2279
2280 if revisions:
2280 if revisions:
2281 # Try doing the filtering in chunks to avoid hitting limits
2281 # Try doing the filtering in chunks to avoid hitting limits
2282 size = 500
2282 size = 500
2283 status_results = []
2283 status_results = []
2284 for chunk in xrange(0, len(revisions), size):
2284 for chunk in xrange(0, len(revisions), size):
2285 status_results += statuses.filter(
2285 status_results += statuses.filter(
2286 ChangesetStatus.revision.in_(
2286 ChangesetStatus.revision.in_(
2287 revisions[chunk: chunk+size])
2287 revisions[chunk: chunk+size])
2288 ).all()
2288 ).all()
2289 else:
2289 else:
2290 status_results = statuses.all()
2290 status_results = statuses.all()
2291
2291
2292 grouped = {}
2292 grouped = {}
2293
2293
2294 # maybe we have open new pullrequest without a status?
2294 # maybe we have open new pullrequest without a status?
2295 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2295 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2296 status_lbl = ChangesetStatus.get_status_lbl(stat)
2296 status_lbl = ChangesetStatus.get_status_lbl(stat)
2297 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2297 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2298 for rev in pr.revisions:
2298 for rev in pr.revisions:
2299 pr_id = pr.pull_request_id
2299 pr_id = pr.pull_request_id
2300 pr_repo = pr.target_repo.repo_name
2300 pr_repo = pr.target_repo.repo_name
2301 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2301 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2302
2302
2303 for stat in status_results:
2303 for stat in status_results:
2304 pr_id = pr_repo = None
2304 pr_id = pr_repo = None
2305 if stat.pull_request:
2305 if stat.pull_request:
2306 pr_id = stat.pull_request.pull_request_id
2306 pr_id = stat.pull_request.pull_request_id
2307 pr_repo = stat.pull_request.target_repo.repo_name
2307 pr_repo = stat.pull_request.target_repo.repo_name
2308 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2308 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2309 pr_id, pr_repo]
2309 pr_id, pr_repo]
2310 return grouped
2310 return grouped
2311
2311
2312 # ==========================================================================
2312 # ==========================================================================
2313 # SCM CACHE INSTANCE
2313 # SCM CACHE INSTANCE
2314 # ==========================================================================
2314 # ==========================================================================
2315
2315
2316 def scm_instance(self, **kwargs):
2316 def scm_instance(self, **kwargs):
2317 import rhodecode
2317 import rhodecode
2318
2318
2319 # Passing a config will not hit the cache currently only used
2319 # Passing a config will not hit the cache currently only used
2320 # for repo2dbmapper
2320 # for repo2dbmapper
2321 config = kwargs.pop('config', None)
2321 config = kwargs.pop('config', None)
2322 cache = kwargs.pop('cache', None)
2322 cache = kwargs.pop('cache', None)
2323 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2323 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2324 # if cache is NOT defined use default global, else we have a full
2324 # if cache is NOT defined use default global, else we have a full
2325 # control over cache behaviour
2325 # control over cache behaviour
2326 if cache is None and full_cache and not config:
2326 if cache is None and full_cache and not config:
2327 return self._get_instance_cached()
2327 return self._get_instance_cached()
2328 return self._get_instance(cache=bool(cache), config=config)
2328 return self._get_instance(cache=bool(cache), config=config)
2329
2329
2330 def _get_instance_cached(self):
2330 def _get_instance_cached(self):
2331 return self._get_instance()
2331 return self._get_instance()
2332
2332
2333 def _get_instance(self, cache=True, config=None):
2333 def _get_instance(self, cache=True, config=None):
2334 config = config or self._config
2334 config = config or self._config
2335 custom_wire = {
2335 custom_wire = {
2336 'cache': cache # controls the vcs.remote cache
2336 'cache': cache # controls the vcs.remote cache
2337 }
2337 }
2338 repo = get_vcs_instance(
2338 repo = get_vcs_instance(
2339 repo_path=safe_str(self.repo_full_path),
2339 repo_path=safe_str(self.repo_full_path),
2340 config=config,
2340 config=config,
2341 with_wire=custom_wire,
2341 with_wire=custom_wire,
2342 create=False,
2342 create=False,
2343 _vcs_alias=self.repo_type)
2343 _vcs_alias=self.repo_type)
2344
2344
2345 return repo
2345 return repo
2346
2346
2347 def __json__(self):
2347 def __json__(self):
2348 return {'landing_rev': self.landing_rev}
2348 return {'landing_rev': self.landing_rev}
2349
2349
2350 def get_dict(self):
2350 def get_dict(self):
2351
2351
2352 # Since we transformed `repo_name` to a hybrid property, we need to
2352 # Since we transformed `repo_name` to a hybrid property, we need to
2353 # keep compatibility with the code which uses `repo_name` field.
2353 # keep compatibility with the code which uses `repo_name` field.
2354
2354
2355 result = super(Repository, self).get_dict()
2355 result = super(Repository, self).get_dict()
2356 result['repo_name'] = result.pop('_repo_name', None)
2356 result['repo_name'] = result.pop('_repo_name', None)
2357 return result
2357 return result
2358
2358
2359
2359
2360 class RepoGroup(Base, BaseModel):
2360 class RepoGroup(Base, BaseModel):
2361 __tablename__ = 'groups'
2361 __tablename__ = 'groups'
2362 __table_args__ = (
2362 __table_args__ = (
2363 UniqueConstraint('group_name', 'group_parent_id'),
2363 UniqueConstraint('group_name', 'group_parent_id'),
2364 CheckConstraint('group_id != group_parent_id'),
2364 CheckConstraint('group_id != group_parent_id'),
2365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2366 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2366 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2367 )
2367 )
2368 __mapper_args__ = {'order_by': 'group_name'}
2368 __mapper_args__ = {'order_by': 'group_name'}
2369
2369
2370 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2370 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2371
2371
2372 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2372 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2373 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2373 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2374 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2374 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2375 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2375 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2376 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2376 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2377 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2377 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2378 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2378 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2379 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2379 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2380 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2380 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2381
2381
2382 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2382 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2383 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2383 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2384 parent_group = relationship('RepoGroup', remote_side=group_id)
2384 parent_group = relationship('RepoGroup', remote_side=group_id)
2385 user = relationship('User')
2385 user = relationship('User')
2386 integrations = relationship('Integration',
2386 integrations = relationship('Integration',
2387 cascade="all, delete, delete-orphan")
2387 cascade="all, delete, delete-orphan")
2388
2388
2389 def __init__(self, group_name='', parent_group=None):
2389 def __init__(self, group_name='', parent_group=None):
2390 self.group_name = group_name
2390 self.group_name = group_name
2391 self.parent_group = parent_group
2391 self.parent_group = parent_group
2392
2392
2393 def __unicode__(self):
2393 def __unicode__(self):
2394 return u"<%s('id:%s:%s')>" % (
2394 return u"<%s('id:%s:%s')>" % (
2395 self.__class__.__name__, self.group_id, self.group_name)
2395 self.__class__.__name__, self.group_id, self.group_name)
2396
2396
2397 @hybrid_property
2397 @hybrid_property
2398 def description_safe(self):
2398 def description_safe(self):
2399 from rhodecode.lib import helpers as h
2399 from rhodecode.lib import helpers as h
2400 return h.escape(self.group_description)
2400 return h.escape(self.group_description)
2401
2401
2402 @classmethod
2402 @classmethod
2403 def _generate_choice(cls, repo_group):
2403 def _generate_choice(cls, repo_group):
2404 from webhelpers.html import literal as _literal
2404 from webhelpers.html import literal as _literal
2405 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2405 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2406 return repo_group.group_id, _name(repo_group.full_path_splitted)
2406 return repo_group.group_id, _name(repo_group.full_path_splitted)
2407
2407
2408 @classmethod
2408 @classmethod
2409 def groups_choices(cls, groups=None, show_empty_group=True):
2409 def groups_choices(cls, groups=None, show_empty_group=True):
2410 if not groups:
2410 if not groups:
2411 groups = cls.query().all()
2411 groups = cls.query().all()
2412
2412
2413 repo_groups = []
2413 repo_groups = []
2414 if show_empty_group:
2414 if show_empty_group:
2415 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2415 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2416
2416
2417 repo_groups.extend([cls._generate_choice(x) for x in groups])
2417 repo_groups.extend([cls._generate_choice(x) for x in groups])
2418
2418
2419 repo_groups = sorted(
2419 repo_groups = sorted(
2420 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2420 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2421 return repo_groups
2421 return repo_groups
2422
2422
2423 @classmethod
2423 @classmethod
2424 def url_sep(cls):
2424 def url_sep(cls):
2425 return URL_SEP
2425 return URL_SEP
2426
2426
2427 @classmethod
2427 @classmethod
2428 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2428 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2429 if case_insensitive:
2429 if case_insensitive:
2430 gr = cls.query().filter(func.lower(cls.group_name)
2430 gr = cls.query().filter(func.lower(cls.group_name)
2431 == func.lower(group_name))
2431 == func.lower(group_name))
2432 else:
2432 else:
2433 gr = cls.query().filter(cls.group_name == group_name)
2433 gr = cls.query().filter(cls.group_name == group_name)
2434 if cache:
2434 if cache:
2435 name_key = _hash_key(group_name)
2435 name_key = _hash_key(group_name)
2436 gr = gr.options(
2436 gr = gr.options(
2437 FromCache("sql_cache_short", "get_group_%s" % name_key))
2437 FromCache("sql_cache_short", "get_group_%s" % name_key))
2438 return gr.scalar()
2438 return gr.scalar()
2439
2439
2440 @classmethod
2440 @classmethod
2441 def get_user_personal_repo_group(cls, user_id):
2441 def get_user_personal_repo_group(cls, user_id):
2442 user = User.get(user_id)
2442 user = User.get(user_id)
2443 if user.username == User.DEFAULT_USER:
2443 if user.username == User.DEFAULT_USER:
2444 return None
2444 return None
2445
2445
2446 return cls.query()\
2446 return cls.query()\
2447 .filter(cls.personal == true()) \
2447 .filter(cls.personal == true()) \
2448 .filter(cls.user == user).scalar()
2448 .filter(cls.user == user).scalar()
2449
2449
2450 @classmethod
2450 @classmethod
2451 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2451 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2452 case_insensitive=True):
2452 case_insensitive=True):
2453 q = RepoGroup.query()
2453 q = RepoGroup.query()
2454
2454
2455 if not isinstance(user_id, Optional):
2455 if not isinstance(user_id, Optional):
2456 q = q.filter(RepoGroup.user_id == user_id)
2456 q = q.filter(RepoGroup.user_id == user_id)
2457
2457
2458 if not isinstance(group_id, Optional):
2458 if not isinstance(group_id, Optional):
2459 q = q.filter(RepoGroup.group_parent_id == group_id)
2459 q = q.filter(RepoGroup.group_parent_id == group_id)
2460
2460
2461 if case_insensitive:
2461 if case_insensitive:
2462 q = q.order_by(func.lower(RepoGroup.group_name))
2462 q = q.order_by(func.lower(RepoGroup.group_name))
2463 else:
2463 else:
2464 q = q.order_by(RepoGroup.group_name)
2464 q = q.order_by(RepoGroup.group_name)
2465 return q.all()
2465 return q.all()
2466
2466
2467 @property
2467 @property
2468 def parents(self):
2468 def parents(self):
2469 parents_recursion_limit = 10
2469 parents_recursion_limit = 10
2470 groups = []
2470 groups = []
2471 if self.parent_group is None:
2471 if self.parent_group is None:
2472 return groups
2472 return groups
2473 cur_gr = self.parent_group
2473 cur_gr = self.parent_group
2474 groups.insert(0, cur_gr)
2474 groups.insert(0, cur_gr)
2475 cnt = 0
2475 cnt = 0
2476 while 1:
2476 while 1:
2477 cnt += 1
2477 cnt += 1
2478 gr = getattr(cur_gr, 'parent_group', None)
2478 gr = getattr(cur_gr, 'parent_group', None)
2479 cur_gr = cur_gr.parent_group
2479 cur_gr = cur_gr.parent_group
2480 if gr is None:
2480 if gr is None:
2481 break
2481 break
2482 if cnt == parents_recursion_limit:
2482 if cnt == parents_recursion_limit:
2483 # this will prevent accidental infinit loops
2483 # this will prevent accidental infinit loops
2484 log.error('more than %s parents found for group %s, stopping '
2484 log.error('more than %s parents found for group %s, stopping '
2485 'recursive parent fetching', parents_recursion_limit, self)
2485 'recursive parent fetching', parents_recursion_limit, self)
2486 break
2486 break
2487
2487
2488 groups.insert(0, gr)
2488 groups.insert(0, gr)
2489 return groups
2489 return groups
2490
2490
2491 @property
2491 @property
2492 def last_db_change(self):
2492 def last_db_change(self):
2493 return self.updated_on
2493 return self.updated_on
2494
2494
2495 @property
2495 @property
2496 def children(self):
2496 def children(self):
2497 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2497 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2498
2498
2499 @property
2499 @property
2500 def name(self):
2500 def name(self):
2501 return self.group_name.split(RepoGroup.url_sep())[-1]
2501 return self.group_name.split(RepoGroup.url_sep())[-1]
2502
2502
2503 @property
2503 @property
2504 def full_path(self):
2504 def full_path(self):
2505 return self.group_name
2505 return self.group_name
2506
2506
2507 @property
2507 @property
2508 def full_path_splitted(self):
2508 def full_path_splitted(self):
2509 return self.group_name.split(RepoGroup.url_sep())
2509 return self.group_name.split(RepoGroup.url_sep())
2510
2510
2511 @property
2511 @property
2512 def repositories(self):
2512 def repositories(self):
2513 return Repository.query()\
2513 return Repository.query()\
2514 .filter(Repository.group == self)\
2514 .filter(Repository.group == self)\
2515 .order_by(Repository.repo_name)
2515 .order_by(Repository.repo_name)
2516
2516
2517 @property
2517 @property
2518 def repositories_recursive_count(self):
2518 def repositories_recursive_count(self):
2519 cnt = self.repositories.count()
2519 cnt = self.repositories.count()
2520
2520
2521 def children_count(group):
2521 def children_count(group):
2522 cnt = 0
2522 cnt = 0
2523 for child in group.children:
2523 for child in group.children:
2524 cnt += child.repositories.count()
2524 cnt += child.repositories.count()
2525 cnt += children_count(child)
2525 cnt += children_count(child)
2526 return cnt
2526 return cnt
2527
2527
2528 return cnt + children_count(self)
2528 return cnt + children_count(self)
2529
2529
2530 def _recursive_objects(self, include_repos=True):
2530 def _recursive_objects(self, include_repos=True):
2531 all_ = []
2531 all_ = []
2532
2532
2533 def _get_members(root_gr):
2533 def _get_members(root_gr):
2534 if include_repos:
2534 if include_repos:
2535 for r in root_gr.repositories:
2535 for r in root_gr.repositories:
2536 all_.append(r)
2536 all_.append(r)
2537 childs = root_gr.children.all()
2537 childs = root_gr.children.all()
2538 if childs:
2538 if childs:
2539 for gr in childs:
2539 for gr in childs:
2540 all_.append(gr)
2540 all_.append(gr)
2541 _get_members(gr)
2541 _get_members(gr)
2542
2542
2543 _get_members(self)
2543 _get_members(self)
2544 return [self] + all_
2544 return [self] + all_
2545
2545
2546 def recursive_groups_and_repos(self):
2546 def recursive_groups_and_repos(self):
2547 """
2547 """
2548 Recursive return all groups, with repositories in those groups
2548 Recursive return all groups, with repositories in those groups
2549 """
2549 """
2550 return self._recursive_objects()
2550 return self._recursive_objects()
2551
2551
2552 def recursive_groups(self):
2552 def recursive_groups(self):
2553 """
2553 """
2554 Returns all children groups for this group including children of children
2554 Returns all children groups for this group including children of children
2555 """
2555 """
2556 return self._recursive_objects(include_repos=False)
2556 return self._recursive_objects(include_repos=False)
2557
2557
2558 def get_new_name(self, group_name):
2558 def get_new_name(self, group_name):
2559 """
2559 """
2560 returns new full group name based on parent and new name
2560 returns new full group name based on parent and new name
2561
2561
2562 :param group_name:
2562 :param group_name:
2563 """
2563 """
2564 path_prefix = (self.parent_group.full_path_splitted if
2564 path_prefix = (self.parent_group.full_path_splitted if
2565 self.parent_group else [])
2565 self.parent_group else [])
2566 return RepoGroup.url_sep().join(path_prefix + [group_name])
2566 return RepoGroup.url_sep().join(path_prefix + [group_name])
2567
2567
2568 def permissions(self, with_admins=True, with_owner=True):
2568 def permissions(self, with_admins=True, with_owner=True):
2569 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2569 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2570 q = q.options(joinedload(UserRepoGroupToPerm.group),
2570 q = q.options(joinedload(UserRepoGroupToPerm.group),
2571 joinedload(UserRepoGroupToPerm.user),
2571 joinedload(UserRepoGroupToPerm.user),
2572 joinedload(UserRepoGroupToPerm.permission),)
2572 joinedload(UserRepoGroupToPerm.permission),)
2573
2573
2574 # get owners and admins and permissions. We do a trick of re-writing
2574 # get owners and admins and permissions. We do a trick of re-writing
2575 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2575 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2576 # has a global reference and changing one object propagates to all
2576 # has a global reference and changing one object propagates to all
2577 # others. This means if admin is also an owner admin_row that change
2577 # others. This means if admin is also an owner admin_row that change
2578 # would propagate to both objects
2578 # would propagate to both objects
2579 perm_rows = []
2579 perm_rows = []
2580 for _usr in q.all():
2580 for _usr in q.all():
2581 usr = AttributeDict(_usr.user.get_dict())
2581 usr = AttributeDict(_usr.user.get_dict())
2582 usr.permission = _usr.permission.permission_name
2582 usr.permission = _usr.permission.permission_name
2583 perm_rows.append(usr)
2583 perm_rows.append(usr)
2584
2584
2585 # filter the perm rows by 'default' first and then sort them by
2585 # filter the perm rows by 'default' first and then sort them by
2586 # admin,write,read,none permissions sorted again alphabetically in
2586 # admin,write,read,none permissions sorted again alphabetically in
2587 # each group
2587 # each group
2588 perm_rows = sorted(perm_rows, key=display_user_sort)
2588 perm_rows = sorted(perm_rows, key=display_user_sort)
2589
2589
2590 _admin_perm = 'group.admin'
2590 _admin_perm = 'group.admin'
2591 owner_row = []
2591 owner_row = []
2592 if with_owner:
2592 if with_owner:
2593 usr = AttributeDict(self.user.get_dict())
2593 usr = AttributeDict(self.user.get_dict())
2594 usr.owner_row = True
2594 usr.owner_row = True
2595 usr.permission = _admin_perm
2595 usr.permission = _admin_perm
2596 owner_row.append(usr)
2596 owner_row.append(usr)
2597
2597
2598 super_admin_rows = []
2598 super_admin_rows = []
2599 if with_admins:
2599 if with_admins:
2600 for usr in User.get_all_super_admins():
2600 for usr in User.get_all_super_admins():
2601 # if this admin is also owner, don't double the record
2601 # if this admin is also owner, don't double the record
2602 if usr.user_id == owner_row[0].user_id:
2602 if usr.user_id == owner_row[0].user_id:
2603 owner_row[0].admin_row = True
2603 owner_row[0].admin_row = True
2604 else:
2604 else:
2605 usr = AttributeDict(usr.get_dict())
2605 usr = AttributeDict(usr.get_dict())
2606 usr.admin_row = True
2606 usr.admin_row = True
2607 usr.permission = _admin_perm
2607 usr.permission = _admin_perm
2608 super_admin_rows.append(usr)
2608 super_admin_rows.append(usr)
2609
2609
2610 return super_admin_rows + owner_row + perm_rows
2610 return super_admin_rows + owner_row + perm_rows
2611
2611
2612 def permission_user_groups(self):
2612 def permission_user_groups(self):
2613 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2613 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2614 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2614 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2615 joinedload(UserGroupRepoGroupToPerm.users_group),
2615 joinedload(UserGroupRepoGroupToPerm.users_group),
2616 joinedload(UserGroupRepoGroupToPerm.permission),)
2616 joinedload(UserGroupRepoGroupToPerm.permission),)
2617
2617
2618 perm_rows = []
2618 perm_rows = []
2619 for _user_group in q.all():
2619 for _user_group in q.all():
2620 usr = AttributeDict(_user_group.users_group.get_dict())
2620 usr = AttributeDict(_user_group.users_group.get_dict())
2621 usr.permission = _user_group.permission.permission_name
2621 usr.permission = _user_group.permission.permission_name
2622 perm_rows.append(usr)
2622 perm_rows.append(usr)
2623
2623
2624 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2624 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2625 return perm_rows
2625 return perm_rows
2626
2626
2627 def get_api_data(self):
2627 def get_api_data(self):
2628 """
2628 """
2629 Common function for generating api data
2629 Common function for generating api data
2630
2630
2631 """
2631 """
2632 group = self
2632 group = self
2633 data = {
2633 data = {
2634 'group_id': group.group_id,
2634 'group_id': group.group_id,
2635 'group_name': group.group_name,
2635 'group_name': group.group_name,
2636 'group_description': group.description_safe,
2636 'group_description': group.description_safe,
2637 'parent_group': group.parent_group.group_name if group.parent_group else None,
2637 'parent_group': group.parent_group.group_name if group.parent_group else None,
2638 'repositories': [x.repo_name for x in group.repositories],
2638 'repositories': [x.repo_name for x in group.repositories],
2639 'owner': group.user.username,
2639 'owner': group.user.username,
2640 }
2640 }
2641 return data
2641 return data
2642
2642
2643
2643
2644 class Permission(Base, BaseModel):
2644 class Permission(Base, BaseModel):
2645 __tablename__ = 'permissions'
2645 __tablename__ = 'permissions'
2646 __table_args__ = (
2646 __table_args__ = (
2647 Index('p_perm_name_idx', 'permission_name'),
2647 Index('p_perm_name_idx', 'permission_name'),
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2650 )
2650 )
2651 PERMS = [
2651 PERMS = [
2652 ('hg.admin', _('RhodeCode Super Administrator')),
2652 ('hg.admin', _('RhodeCode Super Administrator')),
2653
2653
2654 ('repository.none', _('Repository no access')),
2654 ('repository.none', _('Repository no access')),
2655 ('repository.read', _('Repository read access')),
2655 ('repository.read', _('Repository read access')),
2656 ('repository.write', _('Repository write access')),
2656 ('repository.write', _('Repository write access')),
2657 ('repository.admin', _('Repository admin access')),
2657 ('repository.admin', _('Repository admin access')),
2658
2658
2659 ('group.none', _('Repository group no access')),
2659 ('group.none', _('Repository group no access')),
2660 ('group.read', _('Repository group read access')),
2660 ('group.read', _('Repository group read access')),
2661 ('group.write', _('Repository group write access')),
2661 ('group.write', _('Repository group write access')),
2662 ('group.admin', _('Repository group admin access')),
2662 ('group.admin', _('Repository group admin access')),
2663
2663
2664 ('usergroup.none', _('User group no access')),
2664 ('usergroup.none', _('User group no access')),
2665 ('usergroup.read', _('User group read access')),
2665 ('usergroup.read', _('User group read access')),
2666 ('usergroup.write', _('User group write access')),
2666 ('usergroup.write', _('User group write access')),
2667 ('usergroup.admin', _('User group admin access')),
2667 ('usergroup.admin', _('User group admin access')),
2668
2668
2669 ('branch.none', _('Branch no permissions')),
2669 ('branch.none', _('Branch no permissions')),
2670 ('branch.merge', _('Branch access by web merge')),
2670 ('branch.merge', _('Branch access by web merge')),
2671 ('branch.push', _('Branch access by push')),
2671 ('branch.push', _('Branch access by push')),
2672 ('branch.push_force', _('Branch access by push with force')),
2672 ('branch.push_force', _('Branch access by push with force')),
2673
2673
2674 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2674 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2675 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2675 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2676
2676
2677 ('hg.usergroup.create.false', _('User Group creation disabled')),
2677 ('hg.usergroup.create.false', _('User Group creation disabled')),
2678 ('hg.usergroup.create.true', _('User Group creation enabled')),
2678 ('hg.usergroup.create.true', _('User Group creation enabled')),
2679
2679
2680 ('hg.create.none', _('Repository creation disabled')),
2680 ('hg.create.none', _('Repository creation disabled')),
2681 ('hg.create.repository', _('Repository creation enabled')),
2681 ('hg.create.repository', _('Repository creation enabled')),
2682 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2682 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2683 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2683 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2684
2684
2685 ('hg.fork.none', _('Repository forking disabled')),
2685 ('hg.fork.none', _('Repository forking disabled')),
2686 ('hg.fork.repository', _('Repository forking enabled')),
2686 ('hg.fork.repository', _('Repository forking enabled')),
2687
2687
2688 ('hg.register.none', _('Registration disabled')),
2688 ('hg.register.none', _('Registration disabled')),
2689 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2689 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2690 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2690 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2691
2691
2692 ('hg.password_reset.enabled', _('Password reset enabled')),
2692 ('hg.password_reset.enabled', _('Password reset enabled')),
2693 ('hg.password_reset.hidden', _('Password reset hidden')),
2693 ('hg.password_reset.hidden', _('Password reset hidden')),
2694 ('hg.password_reset.disabled', _('Password reset disabled')),
2694 ('hg.password_reset.disabled', _('Password reset disabled')),
2695
2695
2696 ('hg.extern_activate.manual', _('Manual activation of external account')),
2696 ('hg.extern_activate.manual', _('Manual activation of external account')),
2697 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2697 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2698
2698
2699 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2699 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2700 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2700 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2701 ]
2701 ]
2702
2702
2703 # definition of system default permissions for DEFAULT user, created on
2703 # definition of system default permissions for DEFAULT user, created on
2704 # system setup
2704 # system setup
2705 DEFAULT_USER_PERMISSIONS = [
2705 DEFAULT_USER_PERMISSIONS = [
2706 # object perms
2706 # object perms
2707 'repository.read',
2707 'repository.read',
2708 'group.read',
2708 'group.read',
2709 'usergroup.read',
2709 'usergroup.read',
2710 # branch
2710 # branch
2711 'branch.push',
2711 'branch.push',
2712 # global
2712 # global
2713 'hg.create.repository',
2713 'hg.create.repository',
2714 'hg.repogroup.create.false',
2714 'hg.repogroup.create.false',
2715 'hg.usergroup.create.false',
2715 'hg.usergroup.create.false',
2716 'hg.create.write_on_repogroup.true',
2716 'hg.create.write_on_repogroup.true',
2717 'hg.fork.repository',
2717 'hg.fork.repository',
2718 'hg.register.manual_activate',
2718 'hg.register.manual_activate',
2719 'hg.password_reset.enabled',
2719 'hg.password_reset.enabled',
2720 'hg.extern_activate.auto',
2720 'hg.extern_activate.auto',
2721 'hg.inherit_default_perms.true',
2721 'hg.inherit_default_perms.true',
2722 ]
2722 ]
2723
2723
2724 # defines which permissions are more important higher the more important
2724 # defines which permissions are more important higher the more important
2725 # Weight defines which permissions are more important.
2725 # Weight defines which permissions are more important.
2726 # The higher number the more important.
2726 # The higher number the more important.
2727 PERM_WEIGHTS = {
2727 PERM_WEIGHTS = {
2728 'repository.none': 0,
2728 'repository.none': 0,
2729 'repository.read': 1,
2729 'repository.read': 1,
2730 'repository.write': 3,
2730 'repository.write': 3,
2731 'repository.admin': 4,
2731 'repository.admin': 4,
2732
2732
2733 'group.none': 0,
2733 'group.none': 0,
2734 'group.read': 1,
2734 'group.read': 1,
2735 'group.write': 3,
2735 'group.write': 3,
2736 'group.admin': 4,
2736 'group.admin': 4,
2737
2737
2738 'usergroup.none': 0,
2738 'usergroup.none': 0,
2739 'usergroup.read': 1,
2739 'usergroup.read': 1,
2740 'usergroup.write': 3,
2740 'usergroup.write': 3,
2741 'usergroup.admin': 4,
2741 'usergroup.admin': 4,
2742
2742
2743 'branch.none': 0,
2743 'branch.none': 0,
2744 'branch.merge': 1,
2744 'branch.merge': 1,
2745 'branch.push': 3,
2745 'branch.push': 3,
2746 'branch.push_force': 4,
2746 'branch.push_force': 4,
2747
2747
2748 'hg.repogroup.create.false': 0,
2748 'hg.repogroup.create.false': 0,
2749 'hg.repogroup.create.true': 1,
2749 'hg.repogroup.create.true': 1,
2750
2750
2751 'hg.usergroup.create.false': 0,
2751 'hg.usergroup.create.false': 0,
2752 'hg.usergroup.create.true': 1,
2752 'hg.usergroup.create.true': 1,
2753
2753
2754 'hg.fork.none': 0,
2754 'hg.fork.none': 0,
2755 'hg.fork.repository': 1,
2755 'hg.fork.repository': 1,
2756 'hg.create.none': 0,
2756 'hg.create.none': 0,
2757 'hg.create.repository': 1
2757 'hg.create.repository': 1
2758 }
2758 }
2759
2759
2760 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2760 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2761 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2761 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2762 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2762 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2763
2763
2764 def __unicode__(self):
2764 def __unicode__(self):
2765 return u"<%s('%s:%s')>" % (
2765 return u"<%s('%s:%s')>" % (
2766 self.__class__.__name__, self.permission_id, self.permission_name
2766 self.__class__.__name__, self.permission_id, self.permission_name
2767 )
2767 )
2768
2768
2769 @classmethod
2769 @classmethod
2770 def get_by_key(cls, key):
2770 def get_by_key(cls, key):
2771 return cls.query().filter(cls.permission_name == key).scalar()
2771 return cls.query().filter(cls.permission_name == key).scalar()
2772
2772
2773 @classmethod
2773 @classmethod
2774 def get_default_repo_perms(cls, user_id, repo_id=None):
2774 def get_default_repo_perms(cls, user_id, repo_id=None):
2775 q = Session().query(UserRepoToPerm, Repository, Permission)\
2775 q = Session().query(UserRepoToPerm, Repository, Permission)\
2776 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2776 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2777 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2777 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2778 .filter(UserRepoToPerm.user_id == user_id)
2778 .filter(UserRepoToPerm.user_id == user_id)
2779 if repo_id:
2779 if repo_id:
2780 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2780 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2781 return q.all()
2781 return q.all()
2782
2782
2783 @classmethod
2783 @classmethod
2784 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2784 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2785 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2785 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2786 .join(
2786 .join(
2787 Permission,
2787 Permission,
2788 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2788 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2789 .join(
2789 .join(
2790 Repository,
2790 Repository,
2791 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2791 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2792 .join(
2792 .join(
2793 UserGroup,
2793 UserGroup,
2794 UserGroupRepoToPerm.users_group_id ==
2794 UserGroupRepoToPerm.users_group_id ==
2795 UserGroup.users_group_id)\
2795 UserGroup.users_group_id)\
2796 .join(
2796 .join(
2797 UserGroupMember,
2797 UserGroupMember,
2798 UserGroupRepoToPerm.users_group_id ==
2798 UserGroupRepoToPerm.users_group_id ==
2799 UserGroupMember.users_group_id)\
2799 UserGroupMember.users_group_id)\
2800 .filter(
2800 .filter(
2801 UserGroupMember.user_id == user_id,
2801 UserGroupMember.user_id == user_id,
2802 UserGroup.users_group_active == true())
2802 UserGroup.users_group_active == true())
2803 if repo_id:
2803 if repo_id:
2804 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2804 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2805 return q.all()
2805 return q.all()
2806
2806
2807 @classmethod
2807 @classmethod
2808 def get_default_group_perms(cls, user_id, repo_group_id=None):
2808 def get_default_group_perms(cls, user_id, repo_group_id=None):
2809 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2809 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2810 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2810 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2811 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2811 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2812 .filter(UserRepoGroupToPerm.user_id == user_id)
2812 .filter(UserRepoGroupToPerm.user_id == user_id)
2813 if repo_group_id:
2813 if repo_group_id:
2814 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2814 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2815 return q.all()
2815 return q.all()
2816
2816
2817 @classmethod
2817 @classmethod
2818 def get_default_group_perms_from_user_group(
2818 def get_default_group_perms_from_user_group(
2819 cls, user_id, repo_group_id=None):
2819 cls, user_id, repo_group_id=None):
2820 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2820 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2821 .join(
2821 .join(
2822 Permission,
2822 Permission,
2823 UserGroupRepoGroupToPerm.permission_id ==
2823 UserGroupRepoGroupToPerm.permission_id ==
2824 Permission.permission_id)\
2824 Permission.permission_id)\
2825 .join(
2825 .join(
2826 RepoGroup,
2826 RepoGroup,
2827 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2827 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2828 .join(
2828 .join(
2829 UserGroup,
2829 UserGroup,
2830 UserGroupRepoGroupToPerm.users_group_id ==
2830 UserGroupRepoGroupToPerm.users_group_id ==
2831 UserGroup.users_group_id)\
2831 UserGroup.users_group_id)\
2832 .join(
2832 .join(
2833 UserGroupMember,
2833 UserGroupMember,
2834 UserGroupRepoGroupToPerm.users_group_id ==
2834 UserGroupRepoGroupToPerm.users_group_id ==
2835 UserGroupMember.users_group_id)\
2835 UserGroupMember.users_group_id)\
2836 .filter(
2836 .filter(
2837 UserGroupMember.user_id == user_id,
2837 UserGroupMember.user_id == user_id,
2838 UserGroup.users_group_active == true())
2838 UserGroup.users_group_active == true())
2839 if repo_group_id:
2839 if repo_group_id:
2840 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2840 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2841 return q.all()
2841 return q.all()
2842
2842
2843 @classmethod
2843 @classmethod
2844 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2844 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2845 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2845 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2846 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2846 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2847 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2847 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2848 .filter(UserUserGroupToPerm.user_id == user_id)
2848 .filter(UserUserGroupToPerm.user_id == user_id)
2849 if user_group_id:
2849 if user_group_id:
2850 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2850 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2851 return q.all()
2851 return q.all()
2852
2852
2853 @classmethod
2853 @classmethod
2854 def get_default_user_group_perms_from_user_group(
2854 def get_default_user_group_perms_from_user_group(
2855 cls, user_id, user_group_id=None):
2855 cls, user_id, user_group_id=None):
2856 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2856 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2857 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2857 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2858 .join(
2858 .join(
2859 Permission,
2859 Permission,
2860 UserGroupUserGroupToPerm.permission_id ==
2860 UserGroupUserGroupToPerm.permission_id ==
2861 Permission.permission_id)\
2861 Permission.permission_id)\
2862 .join(
2862 .join(
2863 TargetUserGroup,
2863 TargetUserGroup,
2864 UserGroupUserGroupToPerm.target_user_group_id ==
2864 UserGroupUserGroupToPerm.target_user_group_id ==
2865 TargetUserGroup.users_group_id)\
2865 TargetUserGroup.users_group_id)\
2866 .join(
2866 .join(
2867 UserGroup,
2867 UserGroup,
2868 UserGroupUserGroupToPerm.user_group_id ==
2868 UserGroupUserGroupToPerm.user_group_id ==
2869 UserGroup.users_group_id)\
2869 UserGroup.users_group_id)\
2870 .join(
2870 .join(
2871 UserGroupMember,
2871 UserGroupMember,
2872 UserGroupUserGroupToPerm.user_group_id ==
2872 UserGroupUserGroupToPerm.user_group_id ==
2873 UserGroupMember.users_group_id)\
2873 UserGroupMember.users_group_id)\
2874 .filter(
2874 .filter(
2875 UserGroupMember.user_id == user_id,
2875 UserGroupMember.user_id == user_id,
2876 UserGroup.users_group_active == true())
2876 UserGroup.users_group_active == true())
2877 if user_group_id:
2877 if user_group_id:
2878 q = q.filter(
2878 q = q.filter(
2879 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2879 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2880
2880
2881 return q.all()
2881 return q.all()
2882
2882
2883
2883
2884 class UserRepoToPerm(Base, BaseModel):
2884 class UserRepoToPerm(Base, BaseModel):
2885 __tablename__ = 'repo_to_perm'
2885 __tablename__ = 'repo_to_perm'
2886 __table_args__ = (
2886 __table_args__ = (
2887 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2887 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2889 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2889 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2890 )
2890 )
2891 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2892 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2892 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2893 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2893 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2894 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2894 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2895
2895
2896 user = relationship('User')
2896 user = relationship('User')
2897 repository = relationship('Repository')
2897 repository = relationship('Repository')
2898 permission = relationship('Permission')
2898 permission = relationship('Permission')
2899
2899
2900 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
2900 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
2901
2901
2902 @classmethod
2902 @classmethod
2903 def create(cls, user, repository, permission):
2903 def create(cls, user, repository, permission):
2904 n = cls()
2904 n = cls()
2905 n.user = user
2905 n.user = user
2906 n.repository = repository
2906 n.repository = repository
2907 n.permission = permission
2907 n.permission = permission
2908 Session().add(n)
2908 Session().add(n)
2909 return n
2909 return n
2910
2910
2911 def __unicode__(self):
2911 def __unicode__(self):
2912 return u'<%s => %s >' % (self.user, self.repository)
2912 return u'<%s => %s >' % (self.user, self.repository)
2913
2913
2914
2914
2915 class UserUserGroupToPerm(Base, BaseModel):
2915 class UserUserGroupToPerm(Base, BaseModel):
2916 __tablename__ = 'user_user_group_to_perm'
2916 __tablename__ = 'user_user_group_to_perm'
2917 __table_args__ = (
2917 __table_args__ = (
2918 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2918 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2921 )
2921 )
2922 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2922 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2923 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2923 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2924 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2924 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2925 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2925 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2926
2926
2927 user = relationship('User')
2927 user = relationship('User')
2928 user_group = relationship('UserGroup')
2928 user_group = relationship('UserGroup')
2929 permission = relationship('Permission')
2929 permission = relationship('Permission')
2930
2930
2931 @classmethod
2931 @classmethod
2932 def create(cls, user, user_group, permission):
2932 def create(cls, user, user_group, permission):
2933 n = cls()
2933 n = cls()
2934 n.user = user
2934 n.user = user
2935 n.user_group = user_group
2935 n.user_group = user_group
2936 n.permission = permission
2936 n.permission = permission
2937 Session().add(n)
2937 Session().add(n)
2938 return n
2938 return n
2939
2939
2940 def __unicode__(self):
2940 def __unicode__(self):
2941 return u'<%s => %s >' % (self.user, self.user_group)
2941 return u'<%s => %s >' % (self.user, self.user_group)
2942
2942
2943
2943
2944 class UserToPerm(Base, BaseModel):
2944 class UserToPerm(Base, BaseModel):
2945 __tablename__ = 'user_to_perm'
2945 __tablename__ = 'user_to_perm'
2946 __table_args__ = (
2946 __table_args__ = (
2947 UniqueConstraint('user_id', 'permission_id'),
2947 UniqueConstraint('user_id', 'permission_id'),
2948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2950 )
2950 )
2951 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2951 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2952 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2952 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2953 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2953 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2954
2954
2955 user = relationship('User')
2955 user = relationship('User')
2956 permission = relationship('Permission', lazy='joined')
2956 permission = relationship('Permission', lazy='joined')
2957
2957
2958 def __unicode__(self):
2958 def __unicode__(self):
2959 return u'<%s => %s >' % (self.user, self.permission)
2959 return u'<%s => %s >' % (self.user, self.permission)
2960
2960
2961
2961
2962 class UserGroupRepoToPerm(Base, BaseModel):
2962 class UserGroupRepoToPerm(Base, BaseModel):
2963 __tablename__ = 'users_group_repo_to_perm'
2963 __tablename__ = 'users_group_repo_to_perm'
2964 __table_args__ = (
2964 __table_args__ = (
2965 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2965 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2968 )
2968 )
2969 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2969 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2970 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2970 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2972 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2972 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2973
2973
2974 users_group = relationship('UserGroup')
2974 users_group = relationship('UserGroup')
2975 permission = relationship('Permission')
2975 permission = relationship('Permission')
2976 repository = relationship('Repository')
2976 repository = relationship('Repository')
2977
2977
2978 @classmethod
2978 @classmethod
2979 def create(cls, users_group, repository, permission):
2979 def create(cls, users_group, repository, permission):
2980 n = cls()
2980 n = cls()
2981 n.users_group = users_group
2981 n.users_group = users_group
2982 n.repository = repository
2982 n.repository = repository
2983 n.permission = permission
2983 n.permission = permission
2984 Session().add(n)
2984 Session().add(n)
2985 return n
2985 return n
2986
2986
2987 def __unicode__(self):
2987 def __unicode__(self):
2988 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2988 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2989
2989
2990
2990
2991 class UserGroupUserGroupToPerm(Base, BaseModel):
2991 class UserGroupUserGroupToPerm(Base, BaseModel):
2992 __tablename__ = 'user_group_user_group_to_perm'
2992 __tablename__ = 'user_group_user_group_to_perm'
2993 __table_args__ = (
2993 __table_args__ = (
2994 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2994 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2995 CheckConstraint('target_user_group_id != user_group_id'),
2995 CheckConstraint('target_user_group_id != user_group_id'),
2996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2998 )
2998 )
2999 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)
2999 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)
3000 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3000 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3001 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3001 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3002 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3002 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3003
3003
3004 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3004 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3005 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3005 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3006 permission = relationship('Permission')
3006 permission = relationship('Permission')
3007
3007
3008 @classmethod
3008 @classmethod
3009 def create(cls, target_user_group, user_group, permission):
3009 def create(cls, target_user_group, user_group, permission):
3010 n = cls()
3010 n = cls()
3011 n.target_user_group = target_user_group
3011 n.target_user_group = target_user_group
3012 n.user_group = user_group
3012 n.user_group = user_group
3013 n.permission = permission
3013 n.permission = permission
3014 Session().add(n)
3014 Session().add(n)
3015 return n
3015 return n
3016
3016
3017 def __unicode__(self):
3017 def __unicode__(self):
3018 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3018 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3019
3019
3020
3020
3021 class UserGroupToPerm(Base, BaseModel):
3021 class UserGroupToPerm(Base, BaseModel):
3022 __tablename__ = 'users_group_to_perm'
3022 __tablename__ = 'users_group_to_perm'
3023 __table_args__ = (
3023 __table_args__ = (
3024 UniqueConstraint('users_group_id', 'permission_id',),
3024 UniqueConstraint('users_group_id', 'permission_id',),
3025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3027 )
3027 )
3028 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3028 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3029 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3029 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3030 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3030 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3031
3031
3032 users_group = relationship('UserGroup')
3032 users_group = relationship('UserGroup')
3033 permission = relationship('Permission')
3033 permission = relationship('Permission')
3034
3034
3035
3035
3036 class UserRepoGroupToPerm(Base, BaseModel):
3036 class UserRepoGroupToPerm(Base, BaseModel):
3037 __tablename__ = 'user_repo_group_to_perm'
3037 __tablename__ = 'user_repo_group_to_perm'
3038 __table_args__ = (
3038 __table_args__ = (
3039 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3039 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3040 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3040 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3041 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3041 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3042 )
3042 )
3043
3043
3044 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3044 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3046 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3046 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3047 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3047 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3048
3048
3049 user = relationship('User')
3049 user = relationship('User')
3050 group = relationship('RepoGroup')
3050 group = relationship('RepoGroup')
3051 permission = relationship('Permission')
3051 permission = relationship('Permission')
3052
3052
3053 @classmethod
3053 @classmethod
3054 def create(cls, user, repository_group, permission):
3054 def create(cls, user, repository_group, permission):
3055 n = cls()
3055 n = cls()
3056 n.user = user
3056 n.user = user
3057 n.group = repository_group
3057 n.group = repository_group
3058 n.permission = permission
3058 n.permission = permission
3059 Session().add(n)
3059 Session().add(n)
3060 return n
3060 return n
3061
3061
3062
3062
3063 class UserGroupRepoGroupToPerm(Base, BaseModel):
3063 class UserGroupRepoGroupToPerm(Base, BaseModel):
3064 __tablename__ = 'users_group_repo_group_to_perm'
3064 __tablename__ = 'users_group_repo_group_to_perm'
3065 __table_args__ = (
3065 __table_args__ = (
3066 UniqueConstraint('users_group_id', 'group_id'),
3066 UniqueConstraint('users_group_id', 'group_id'),
3067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3069 )
3069 )
3070
3070
3071 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)
3071 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)
3072 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3072 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3073 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3073 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3074 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3074 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3075
3075
3076 users_group = relationship('UserGroup')
3076 users_group = relationship('UserGroup')
3077 permission = relationship('Permission')
3077 permission = relationship('Permission')
3078 group = relationship('RepoGroup')
3078 group = relationship('RepoGroup')
3079
3079
3080 @classmethod
3080 @classmethod
3081 def create(cls, user_group, repository_group, permission):
3081 def create(cls, user_group, repository_group, permission):
3082 n = cls()
3082 n = cls()
3083 n.users_group = user_group
3083 n.users_group = user_group
3084 n.group = repository_group
3084 n.group = repository_group
3085 n.permission = permission
3085 n.permission = permission
3086 Session().add(n)
3086 Session().add(n)
3087 return n
3087 return n
3088
3088
3089 def __unicode__(self):
3089 def __unicode__(self):
3090 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3090 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3091
3091
3092
3092
3093 class Statistics(Base, BaseModel):
3093 class Statistics(Base, BaseModel):
3094 __tablename__ = 'statistics'
3094 __tablename__ = 'statistics'
3095 __table_args__ = (
3095 __table_args__ = (
3096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3098 )
3098 )
3099 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3099 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3100 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3100 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3101 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3101 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3102 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3102 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3103 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3103 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3104 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3104 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3105
3105
3106 repository = relationship('Repository', single_parent=True)
3106 repository = relationship('Repository', single_parent=True)
3107
3107
3108
3108
3109 class UserFollowing(Base, BaseModel):
3109 class UserFollowing(Base, BaseModel):
3110 __tablename__ = 'user_followings'
3110 __tablename__ = 'user_followings'
3111 __table_args__ = (
3111 __table_args__ = (
3112 UniqueConstraint('user_id', 'follows_repository_id'),
3112 UniqueConstraint('user_id', 'follows_repository_id'),
3113 UniqueConstraint('user_id', 'follows_user_id'),
3113 UniqueConstraint('user_id', 'follows_user_id'),
3114 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3114 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3115 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3115 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3116 )
3116 )
3117
3117
3118 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3118 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3119 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3119 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3120 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3120 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3121 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3121 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3122 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3122 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3123
3123
3124 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3124 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3125
3125
3126 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3126 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3127 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3127 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3128
3128
3129 @classmethod
3129 @classmethod
3130 def get_repo_followers(cls, repo_id):
3130 def get_repo_followers(cls, repo_id):
3131 return cls.query().filter(cls.follows_repo_id == repo_id)
3131 return cls.query().filter(cls.follows_repo_id == repo_id)
3132
3132
3133
3133
3134 class CacheKey(Base, BaseModel):
3134 class CacheKey(Base, BaseModel):
3135 __tablename__ = 'cache_invalidation'
3135 __tablename__ = 'cache_invalidation'
3136 __table_args__ = (
3136 __table_args__ = (
3137 UniqueConstraint('cache_key'),
3137 UniqueConstraint('cache_key'),
3138 Index('key_idx', 'cache_key'),
3138 Index('key_idx', 'cache_key'),
3139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3140 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3140 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3141 )
3141 )
3142 CACHE_TYPE_ATOM = 'ATOM'
3142 CACHE_TYPE_ATOM = 'ATOM'
3143 CACHE_TYPE_RSS = 'RSS'
3143 CACHE_TYPE_RSS = 'RSS'
3144 CACHE_TYPE_README = 'README'
3144 CACHE_TYPE_README = 'README'
3145
3145
3146 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3146 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3147 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3147 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3148 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3148 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3149 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3149 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3150
3150
3151 def __init__(self, cache_key, cache_args=''):
3151 def __init__(self, cache_key, cache_args=''):
3152 self.cache_key = cache_key
3152 self.cache_key = cache_key
3153 self.cache_args = cache_args
3153 self.cache_args = cache_args
3154 self.cache_active = False
3154 self.cache_active = False
3155
3155
3156 def __unicode__(self):
3156 def __unicode__(self):
3157 return u"<%s('%s:%s[%s]')>" % (
3157 return u"<%s('%s:%s[%s]')>" % (
3158 self.__class__.__name__,
3158 self.__class__.__name__,
3159 self.cache_id, self.cache_key, self.cache_active)
3159 self.cache_id, self.cache_key, self.cache_active)
3160
3160
3161 def _cache_key_partition(self):
3161 def _cache_key_partition(self):
3162 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3162 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3163 return prefix, repo_name, suffix
3163 return prefix, repo_name, suffix
3164
3164
3165 def get_prefix(self):
3165 def get_prefix(self):
3166 """
3166 """
3167 Try to extract prefix from existing cache key. The key could consist
3167 Try to extract prefix from existing cache key. The key could consist
3168 of prefix, repo_name, suffix
3168 of prefix, repo_name, suffix
3169 """
3169 """
3170 # this returns prefix, repo_name, suffix
3170 # this returns prefix, repo_name, suffix
3171 return self._cache_key_partition()[0]
3171 return self._cache_key_partition()[0]
3172
3172
3173 def get_suffix(self):
3173 def get_suffix(self):
3174 """
3174 """
3175 get suffix that might have been used in _get_cache_key to
3175 get suffix that might have been used in _get_cache_key to
3176 generate self.cache_key. Only used for informational purposes
3176 generate self.cache_key. Only used for informational purposes
3177 in repo_edit.mako.
3177 in repo_edit.mako.
3178 """
3178 """
3179 # prefix, repo_name, suffix
3179 # prefix, repo_name, suffix
3180 return self._cache_key_partition()[2]
3180 return self._cache_key_partition()[2]
3181
3181
3182 @classmethod
3182 @classmethod
3183 def delete_all_cache(cls):
3183 def delete_all_cache(cls):
3184 """
3184 """
3185 Delete all cache keys from database.
3185 Delete all cache keys from database.
3186 Should only be run when all instances are down and all entries
3186 Should only be run when all instances are down and all entries
3187 thus stale.
3187 thus stale.
3188 """
3188 """
3189 cls.query().delete()
3189 cls.query().delete()
3190 Session().commit()
3190 Session().commit()
3191
3191
3192 @classmethod
3192 @classmethod
3193 def get_cache_key(cls, repo_name, cache_type):
3193 def get_cache_key(cls, repo_name, cache_type):
3194 """
3194 """
3195
3195
3196 Generate a cache key for this process of RhodeCode instance.
3196 Generate a cache key for this process of RhodeCode instance.
3197 Prefix most likely will be process id or maybe explicitly set
3197 Prefix most likely will be process id or maybe explicitly set
3198 instance_id from .ini file.
3198 instance_id from .ini file.
3199 """
3199 """
3200 import rhodecode
3200 import rhodecode
3201 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3201 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3202
3202
3203 repo_as_unicode = safe_unicode(repo_name)
3203 repo_as_unicode = safe_unicode(repo_name)
3204 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3204 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3205 if cache_type else repo_as_unicode
3205 if cache_type else repo_as_unicode
3206
3206
3207 return u'{}{}'.format(prefix, key)
3207 return u'{}{}'.format(prefix, key)
3208
3208
3209 @classmethod
3209 @classmethod
3210 def set_invalidate(cls, repo_name, delete=False):
3210 def set_invalidate(cls, repo_name, delete=False):
3211 """
3211 """
3212 Mark all caches of a repo as invalid in the database.
3212 Mark all caches of a repo as invalid in the database.
3213 """
3213 """
3214
3214
3215 try:
3215 try:
3216 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3216 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3217 if delete:
3217 if delete:
3218 log.debug('cache objects deleted for repo %s',
3218 log.debug('cache objects deleted for repo %s',
3219 safe_str(repo_name))
3219 safe_str(repo_name))
3220 qry.delete()
3220 qry.delete()
3221 else:
3221 else:
3222 log.debug('cache objects marked as invalid for repo %s',
3222 log.debug('cache objects marked as invalid for repo %s',
3223 safe_str(repo_name))
3223 safe_str(repo_name))
3224 qry.update({"cache_active": False})
3224 qry.update({"cache_active": False})
3225
3225
3226 Session().commit()
3226 Session().commit()
3227 except Exception:
3227 except Exception:
3228 log.exception(
3228 log.exception(
3229 'Cache key invalidation failed for repository %s',
3229 'Cache key invalidation failed for repository %s',
3230 safe_str(repo_name))
3230 safe_str(repo_name))
3231 Session().rollback()
3231 Session().rollback()
3232
3232
3233 @classmethod
3233 @classmethod
3234 def get_active_cache(cls, cache_key):
3234 def get_active_cache(cls, cache_key):
3235 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3235 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3236 if inv_obj:
3236 if inv_obj:
3237 return inv_obj
3237 return inv_obj
3238 return None
3238 return None
3239
3239
3240
3240
3241 class ChangesetComment(Base, BaseModel):
3241 class ChangesetComment(Base, BaseModel):
3242 __tablename__ = 'changeset_comments'
3242 __tablename__ = 'changeset_comments'
3243 __table_args__ = (
3243 __table_args__ = (
3244 Index('cc_revision_idx', 'revision'),
3244 Index('cc_revision_idx', 'revision'),
3245 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3245 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3246 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3246 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3247 )
3247 )
3248
3248
3249 COMMENT_OUTDATED = u'comment_outdated'
3249 COMMENT_OUTDATED = u'comment_outdated'
3250 COMMENT_TYPE_NOTE = u'note'
3250 COMMENT_TYPE_NOTE = u'note'
3251 COMMENT_TYPE_TODO = u'todo'
3251 COMMENT_TYPE_TODO = u'todo'
3252 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3252 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3253
3253
3254 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3254 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3255 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3255 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3256 revision = Column('revision', String(40), nullable=True)
3256 revision = Column('revision', String(40), nullable=True)
3257 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3257 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3258 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3258 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3259 line_no = Column('line_no', Unicode(10), nullable=True)
3259 line_no = Column('line_no', Unicode(10), nullable=True)
3260 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3260 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3261 f_path = Column('f_path', Unicode(1000), nullable=True)
3261 f_path = Column('f_path', Unicode(1000), nullable=True)
3262 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3262 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3263 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3263 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3266 renderer = Column('renderer', Unicode(64), nullable=True)
3266 renderer = Column('renderer', Unicode(64), nullable=True)
3267 display_state = Column('display_state', Unicode(128), nullable=True)
3267 display_state = Column('display_state', Unicode(128), nullable=True)
3268
3268
3269 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3269 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3270 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3270 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3271 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3271 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3272 author = relationship('User', lazy='joined')
3272 author = relationship('User', lazy='joined')
3273 repo = relationship('Repository')
3273 repo = relationship('Repository')
3274 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3274 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3275 pull_request = relationship('PullRequest', lazy='joined')
3275 pull_request = relationship('PullRequest', lazy='joined')
3276 pull_request_version = relationship('PullRequestVersion')
3276 pull_request_version = relationship('PullRequestVersion')
3277
3277
3278 @classmethod
3278 @classmethod
3279 def get_users(cls, revision=None, pull_request_id=None):
3279 def get_users(cls, revision=None, pull_request_id=None):
3280 """
3280 """
3281 Returns user associated with this ChangesetComment. ie those
3281 Returns user associated with this ChangesetComment. ie those
3282 who actually commented
3282 who actually commented
3283
3283
3284 :param cls:
3284 :param cls:
3285 :param revision:
3285 :param revision:
3286 """
3286 """
3287 q = Session().query(User)\
3287 q = Session().query(User)\
3288 .join(ChangesetComment.author)
3288 .join(ChangesetComment.author)
3289 if revision:
3289 if revision:
3290 q = q.filter(cls.revision == revision)
3290 q = q.filter(cls.revision == revision)
3291 elif pull_request_id:
3291 elif pull_request_id:
3292 q = q.filter(cls.pull_request_id == pull_request_id)
3292 q = q.filter(cls.pull_request_id == pull_request_id)
3293 return q.all()
3293 return q.all()
3294
3294
3295 @classmethod
3295 @classmethod
3296 def get_index_from_version(cls, pr_version, versions):
3296 def get_index_from_version(cls, pr_version, versions):
3297 num_versions = [x.pull_request_version_id for x in versions]
3297 num_versions = [x.pull_request_version_id for x in versions]
3298 try:
3298 try:
3299 return num_versions.index(pr_version) +1
3299 return num_versions.index(pr_version) +1
3300 except (IndexError, ValueError):
3300 except (IndexError, ValueError):
3301 return
3301 return
3302
3302
3303 @property
3303 @property
3304 def outdated(self):
3304 def outdated(self):
3305 return self.display_state == self.COMMENT_OUTDATED
3305 return self.display_state == self.COMMENT_OUTDATED
3306
3306
3307 def outdated_at_version(self, version):
3307 def outdated_at_version(self, version):
3308 """
3308 """
3309 Checks if comment is outdated for given pull request version
3309 Checks if comment is outdated for given pull request version
3310 """
3310 """
3311 return self.outdated and self.pull_request_version_id != version
3311 return self.outdated and self.pull_request_version_id != version
3312
3312
3313 def older_than_version(self, version):
3313 def older_than_version(self, version):
3314 """
3314 """
3315 Checks if comment is made from previous version than given
3315 Checks if comment is made from previous version than given
3316 """
3316 """
3317 if version is None:
3317 if version is None:
3318 return self.pull_request_version_id is not None
3318 return self.pull_request_version_id is not None
3319
3319
3320 return self.pull_request_version_id < version
3320 return self.pull_request_version_id < version
3321
3321
3322 @property
3322 @property
3323 def resolved(self):
3323 def resolved(self):
3324 return self.resolved_by[0] if self.resolved_by else None
3324 return self.resolved_by[0] if self.resolved_by else None
3325
3325
3326 @property
3326 @property
3327 def is_todo(self):
3327 def is_todo(self):
3328 return self.comment_type == self.COMMENT_TYPE_TODO
3328 return self.comment_type == self.COMMENT_TYPE_TODO
3329
3329
3330 @property
3330 @property
3331 def is_inline(self):
3331 def is_inline(self):
3332 return self.line_no and self.f_path
3332 return self.line_no and self.f_path
3333
3333
3334 def get_index_version(self, versions):
3334 def get_index_version(self, versions):
3335 return self.get_index_from_version(
3335 return self.get_index_from_version(
3336 self.pull_request_version_id, versions)
3336 self.pull_request_version_id, versions)
3337
3337
3338 def __repr__(self):
3338 def __repr__(self):
3339 if self.comment_id:
3339 if self.comment_id:
3340 return '<DB:Comment #%s>' % self.comment_id
3340 return '<DB:Comment #%s>' % self.comment_id
3341 else:
3341 else:
3342 return '<DB:Comment at %#x>' % id(self)
3342 return '<DB:Comment at %#x>' % id(self)
3343
3343
3344 def get_api_data(self):
3344 def get_api_data(self):
3345 comment = self
3345 comment = self
3346 data = {
3346 data = {
3347 'comment_id': comment.comment_id,
3347 'comment_id': comment.comment_id,
3348 'comment_type': comment.comment_type,
3348 'comment_type': comment.comment_type,
3349 'comment_text': comment.text,
3349 'comment_text': comment.text,
3350 'comment_status': comment.status_change,
3350 'comment_status': comment.status_change,
3351 'comment_f_path': comment.f_path,
3351 'comment_f_path': comment.f_path,
3352 'comment_lineno': comment.line_no,
3352 'comment_lineno': comment.line_no,
3353 'comment_author': comment.author,
3353 'comment_author': comment.author,
3354 'comment_created_on': comment.created_on
3354 'comment_created_on': comment.created_on
3355 }
3355 }
3356 return data
3356 return data
3357
3357
3358 def __json__(self):
3358 def __json__(self):
3359 data = dict()
3359 data = dict()
3360 data.update(self.get_api_data())
3360 data.update(self.get_api_data())
3361 return data
3361 return data
3362
3362
3363
3363
3364 class ChangesetStatus(Base, BaseModel):
3364 class ChangesetStatus(Base, BaseModel):
3365 __tablename__ = 'changeset_statuses'
3365 __tablename__ = 'changeset_statuses'
3366 __table_args__ = (
3366 __table_args__ = (
3367 Index('cs_revision_idx', 'revision'),
3367 Index('cs_revision_idx', 'revision'),
3368 Index('cs_version_idx', 'version'),
3368 Index('cs_version_idx', 'version'),
3369 UniqueConstraint('repo_id', 'revision', 'version'),
3369 UniqueConstraint('repo_id', 'revision', 'version'),
3370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3372 )
3372 )
3373 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3373 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3374 STATUS_APPROVED = 'approved'
3374 STATUS_APPROVED = 'approved'
3375 STATUS_REJECTED = 'rejected'
3375 STATUS_REJECTED = 'rejected'
3376 STATUS_UNDER_REVIEW = 'under_review'
3376 STATUS_UNDER_REVIEW = 'under_review'
3377
3377
3378 STATUSES = [
3378 STATUSES = [
3379 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3379 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3380 (STATUS_APPROVED, _("Approved")),
3380 (STATUS_APPROVED, _("Approved")),
3381 (STATUS_REJECTED, _("Rejected")),
3381 (STATUS_REJECTED, _("Rejected")),
3382 (STATUS_UNDER_REVIEW, _("Under Review")),
3382 (STATUS_UNDER_REVIEW, _("Under Review")),
3383 ]
3383 ]
3384
3384
3385 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3385 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3386 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3386 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3388 revision = Column('revision', String(40), nullable=False)
3388 revision = Column('revision', String(40), nullable=False)
3389 status = Column('status', String(128), nullable=False, default=DEFAULT)
3389 status = Column('status', String(128), nullable=False, default=DEFAULT)
3390 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3390 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3391 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3391 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3392 version = Column('version', Integer(), nullable=False, default=0)
3392 version = Column('version', Integer(), nullable=False, default=0)
3393 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3393 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3394
3394
3395 author = relationship('User', lazy='joined')
3395 author = relationship('User', lazy='joined')
3396 repo = relationship('Repository')
3396 repo = relationship('Repository')
3397 comment = relationship('ChangesetComment', lazy='joined')
3397 comment = relationship('ChangesetComment', lazy='joined')
3398 pull_request = relationship('PullRequest', lazy='joined')
3398 pull_request = relationship('PullRequest', lazy='joined')
3399
3399
3400 def __unicode__(self):
3400 def __unicode__(self):
3401 return u"<%s('%s[v%s]:%s')>" % (
3401 return u"<%s('%s[v%s]:%s')>" % (
3402 self.__class__.__name__,
3402 self.__class__.__name__,
3403 self.status, self.version, self.author
3403 self.status, self.version, self.author
3404 )
3404 )
3405
3405
3406 @classmethod
3406 @classmethod
3407 def get_status_lbl(cls, value):
3407 def get_status_lbl(cls, value):
3408 return dict(cls.STATUSES).get(value)
3408 return dict(cls.STATUSES).get(value)
3409
3409
3410 @property
3410 @property
3411 def status_lbl(self):
3411 def status_lbl(self):
3412 return ChangesetStatus.get_status_lbl(self.status)
3412 return ChangesetStatus.get_status_lbl(self.status)
3413
3413
3414 def get_api_data(self):
3414 def get_api_data(self):
3415 status = self
3415 status = self
3416 data = {
3416 data = {
3417 'status_id': status.changeset_status_id,
3417 'status_id': status.changeset_status_id,
3418 'status': status.status,
3418 'status': status.status,
3419 }
3419 }
3420 return data
3420 return data
3421
3421
3422 def __json__(self):
3422 def __json__(self):
3423 data = dict()
3423 data = dict()
3424 data.update(self.get_api_data())
3424 data.update(self.get_api_data())
3425 return data
3425 return data
3426
3426
3427
3427
3428 class _PullRequestBase(BaseModel):
3428 class _PullRequestBase(BaseModel):
3429 """
3429 """
3430 Common attributes of pull request and version entries.
3430 Common attributes of pull request and version entries.
3431 """
3431 """
3432
3432
3433 # .status values
3433 # .status values
3434 STATUS_NEW = u'new'
3434 STATUS_NEW = u'new'
3435 STATUS_OPEN = u'open'
3435 STATUS_OPEN = u'open'
3436 STATUS_CLOSED = u'closed'
3436 STATUS_CLOSED = u'closed'
3437
3437
3438 title = Column('title', Unicode(255), nullable=True)
3438 title = Column('title', Unicode(255), nullable=True)
3439 description = Column(
3439 description = Column(
3440 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3440 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3441 nullable=True)
3441 nullable=True)
3442 # new/open/closed status of pull request (not approve/reject/etc)
3442 # new/open/closed status of pull request (not approve/reject/etc)
3443 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3443 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3444 created_on = Column(
3444 created_on = Column(
3445 'created_on', DateTime(timezone=False), nullable=False,
3445 'created_on', DateTime(timezone=False), nullable=False,
3446 default=datetime.datetime.now)
3446 default=datetime.datetime.now)
3447 updated_on = Column(
3447 updated_on = Column(
3448 'updated_on', DateTime(timezone=False), nullable=False,
3448 'updated_on', DateTime(timezone=False), nullable=False,
3449 default=datetime.datetime.now)
3449 default=datetime.datetime.now)
3450
3450
3451 @declared_attr
3451 @declared_attr
3452 def user_id(cls):
3452 def user_id(cls):
3453 return Column(
3453 return Column(
3454 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3454 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3455 unique=None)
3455 unique=None)
3456
3456
3457 # 500 revisions max
3457 # 500 revisions max
3458 _revisions = Column(
3458 _revisions = Column(
3459 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3459 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3460
3460
3461 @declared_attr
3461 @declared_attr
3462 def source_repo_id(cls):
3462 def source_repo_id(cls):
3463 # TODO: dan: rename column to source_repo_id
3463 # TODO: dan: rename column to source_repo_id
3464 return Column(
3464 return Column(
3465 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3465 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3466 nullable=False)
3466 nullable=False)
3467
3467
3468 source_ref = Column('org_ref', Unicode(255), nullable=False)
3468 source_ref = Column('org_ref', Unicode(255), nullable=False)
3469
3469
3470 @declared_attr
3470 @declared_attr
3471 def target_repo_id(cls):
3471 def target_repo_id(cls):
3472 # TODO: dan: rename column to target_repo_id
3472 # TODO: dan: rename column to target_repo_id
3473 return Column(
3473 return Column(
3474 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3474 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3475 nullable=False)
3475 nullable=False)
3476
3476
3477 target_ref = Column('other_ref', Unicode(255), nullable=False)
3477 target_ref = Column('other_ref', Unicode(255), nullable=False)
3478 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3478 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3479
3479
3480 # TODO: dan: rename column to last_merge_source_rev
3480 # TODO: dan: rename column to last_merge_source_rev
3481 _last_merge_source_rev = Column(
3481 _last_merge_source_rev = Column(
3482 'last_merge_org_rev', String(40), nullable=True)
3482 'last_merge_org_rev', String(40), nullable=True)
3483 # TODO: dan: rename column to last_merge_target_rev
3483 # TODO: dan: rename column to last_merge_target_rev
3484 _last_merge_target_rev = Column(
3484 _last_merge_target_rev = Column(
3485 'last_merge_other_rev', String(40), nullable=True)
3485 'last_merge_other_rev', String(40), nullable=True)
3486 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3486 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3487 merge_rev = Column('merge_rev', String(40), nullable=True)
3487 merge_rev = Column('merge_rev', String(40), nullable=True)
3488
3488
3489 reviewer_data = Column(
3489 reviewer_data = Column(
3490 'reviewer_data_json', MutationObj.as_mutable(
3490 'reviewer_data_json', MutationObj.as_mutable(
3491 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3491 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3492
3492
3493 @property
3493 @property
3494 def reviewer_data_json(self):
3494 def reviewer_data_json(self):
3495 return json.dumps(self.reviewer_data)
3495 return json.dumps(self.reviewer_data)
3496
3496
3497 @hybrid_property
3497 @hybrid_property
3498 def description_safe(self):
3498 def description_safe(self):
3499 from rhodecode.lib import helpers as h
3499 from rhodecode.lib import helpers as h
3500 return h.escape(self.description)
3500 return h.escape(self.description)
3501
3501
3502 @hybrid_property
3502 @hybrid_property
3503 def revisions(self):
3503 def revisions(self):
3504 return self._revisions.split(':') if self._revisions else []
3504 return self._revisions.split(':') if self._revisions else []
3505
3505
3506 @revisions.setter
3506 @revisions.setter
3507 def revisions(self, val):
3507 def revisions(self, val):
3508 self._revisions = ':'.join(val)
3508 self._revisions = ':'.join(val)
3509
3509
3510 @hybrid_property
3510 @hybrid_property
3511 def last_merge_status(self):
3511 def last_merge_status(self):
3512 return safe_int(self._last_merge_status)
3512 return safe_int(self._last_merge_status)
3513
3513
3514 @last_merge_status.setter
3514 @last_merge_status.setter
3515 def last_merge_status(self, val):
3515 def last_merge_status(self, val):
3516 self._last_merge_status = val
3516 self._last_merge_status = val
3517
3517
3518 @declared_attr
3518 @declared_attr
3519 def author(cls):
3519 def author(cls):
3520 return relationship('User', lazy='joined')
3520 return relationship('User', lazy='joined')
3521
3521
3522 @declared_attr
3522 @declared_attr
3523 def source_repo(cls):
3523 def source_repo(cls):
3524 return relationship(
3524 return relationship(
3525 'Repository',
3525 'Repository',
3526 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3526 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3527
3527
3528 @property
3528 @property
3529 def source_ref_parts(self):
3529 def source_ref_parts(self):
3530 return self.unicode_to_reference(self.source_ref)
3530 return self.unicode_to_reference(self.source_ref)
3531
3531
3532 @declared_attr
3532 @declared_attr
3533 def target_repo(cls):
3533 def target_repo(cls):
3534 return relationship(
3534 return relationship(
3535 'Repository',
3535 'Repository',
3536 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3536 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3537
3537
3538 @property
3538 @property
3539 def target_ref_parts(self):
3539 def target_ref_parts(self):
3540 return self.unicode_to_reference(self.target_ref)
3540 return self.unicode_to_reference(self.target_ref)
3541
3541
3542 @property
3542 @property
3543 def shadow_merge_ref(self):
3543 def shadow_merge_ref(self):
3544 return self.unicode_to_reference(self._shadow_merge_ref)
3544 return self.unicode_to_reference(self._shadow_merge_ref)
3545
3545
3546 @shadow_merge_ref.setter
3546 @shadow_merge_ref.setter
3547 def shadow_merge_ref(self, ref):
3547 def shadow_merge_ref(self, ref):
3548 self._shadow_merge_ref = self.reference_to_unicode(ref)
3548 self._shadow_merge_ref = self.reference_to_unicode(ref)
3549
3549
3550 def unicode_to_reference(self, raw):
3550 def unicode_to_reference(self, raw):
3551 """
3551 """
3552 Convert a unicode (or string) to a reference object.
3552 Convert a unicode (or string) to a reference object.
3553 If unicode evaluates to False it returns None.
3553 If unicode evaluates to False it returns None.
3554 """
3554 """
3555 if raw:
3555 if raw:
3556 refs = raw.split(':')
3556 refs = raw.split(':')
3557 return Reference(*refs)
3557 return Reference(*refs)
3558 else:
3558 else:
3559 return None
3559 return None
3560
3560
3561 def reference_to_unicode(self, ref):
3561 def reference_to_unicode(self, ref):
3562 """
3562 """
3563 Convert a reference object to unicode.
3563 Convert a reference object to unicode.
3564 If reference is None it returns None.
3564 If reference is None it returns None.
3565 """
3565 """
3566 if ref:
3566 if ref:
3567 return u':'.join(ref)
3567 return u':'.join(ref)
3568 else:
3568 else:
3569 return None
3569 return None
3570
3570
3571 def get_api_data(self, with_merge_state=True):
3571 def get_api_data(self, with_merge_state=True):
3572 from rhodecode.model.pull_request import PullRequestModel
3572 from rhodecode.model.pull_request import PullRequestModel
3573
3573
3574 pull_request = self
3574 pull_request = self
3575 if with_merge_state:
3575 if with_merge_state:
3576 merge_status = PullRequestModel().merge_status(pull_request)
3576 merge_status = PullRequestModel().merge_status(pull_request)
3577 merge_state = {
3577 merge_state = {
3578 'status': merge_status[0],
3578 'status': merge_status[0],
3579 'message': safe_unicode(merge_status[1]),
3579 'message': safe_unicode(merge_status[1]),
3580 }
3580 }
3581 else:
3581 else:
3582 merge_state = {'status': 'not_available',
3582 merge_state = {'status': 'not_available',
3583 'message': 'not_available'}
3583 'message': 'not_available'}
3584
3584
3585 merge_data = {
3585 merge_data = {
3586 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3586 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3587 'reference': (
3587 'reference': (
3588 pull_request.shadow_merge_ref._asdict()
3588 pull_request.shadow_merge_ref._asdict()
3589 if pull_request.shadow_merge_ref else None),
3589 if pull_request.shadow_merge_ref else None),
3590 }
3590 }
3591
3591
3592 data = {
3592 data = {
3593 'pull_request_id': pull_request.pull_request_id,
3593 'pull_request_id': pull_request.pull_request_id,
3594 'url': PullRequestModel().get_url(pull_request),
3594 'url': PullRequestModel().get_url(pull_request),
3595 'title': pull_request.title,
3595 'title': pull_request.title,
3596 'description': pull_request.description,
3596 'description': pull_request.description,
3597 'status': pull_request.status,
3597 'status': pull_request.status,
3598 'created_on': pull_request.created_on,
3598 'created_on': pull_request.created_on,
3599 'updated_on': pull_request.updated_on,
3599 'updated_on': pull_request.updated_on,
3600 'commit_ids': pull_request.revisions,
3600 'commit_ids': pull_request.revisions,
3601 'review_status': pull_request.calculated_review_status(),
3601 'review_status': pull_request.calculated_review_status(),
3602 'mergeable': merge_state,
3602 'mergeable': merge_state,
3603 'source': {
3603 'source': {
3604 'clone_url': pull_request.source_repo.clone_url(),
3604 'clone_url': pull_request.source_repo.clone_url(),
3605 'repository': pull_request.source_repo.repo_name,
3605 'repository': pull_request.source_repo.repo_name,
3606 'reference': {
3606 'reference': {
3607 'name': pull_request.source_ref_parts.name,
3607 'name': pull_request.source_ref_parts.name,
3608 'type': pull_request.source_ref_parts.type,
3608 'type': pull_request.source_ref_parts.type,
3609 'commit_id': pull_request.source_ref_parts.commit_id,
3609 'commit_id': pull_request.source_ref_parts.commit_id,
3610 },
3610 },
3611 },
3611 },
3612 'target': {
3612 'target': {
3613 'clone_url': pull_request.target_repo.clone_url(),
3613 'clone_url': pull_request.target_repo.clone_url(),
3614 'repository': pull_request.target_repo.repo_name,
3614 'repository': pull_request.target_repo.repo_name,
3615 'reference': {
3615 'reference': {
3616 'name': pull_request.target_ref_parts.name,
3616 'name': pull_request.target_ref_parts.name,
3617 'type': pull_request.target_ref_parts.type,
3617 'type': pull_request.target_ref_parts.type,
3618 'commit_id': pull_request.target_ref_parts.commit_id,
3618 'commit_id': pull_request.target_ref_parts.commit_id,
3619 },
3619 },
3620 },
3620 },
3621 'merge': merge_data,
3621 'merge': merge_data,
3622 'author': pull_request.author.get_api_data(include_secrets=False,
3622 'author': pull_request.author.get_api_data(include_secrets=False,
3623 details='basic'),
3623 details='basic'),
3624 'reviewers': [
3624 'reviewers': [
3625 {
3625 {
3626 'user': reviewer.get_api_data(include_secrets=False,
3626 'user': reviewer.get_api_data(include_secrets=False,
3627 details='basic'),
3627 details='basic'),
3628 'reasons': reasons,
3628 'reasons': reasons,
3629 'review_status': st[0][1].status if st else 'not_reviewed',
3629 'review_status': st[0][1].status if st else 'not_reviewed',
3630 }
3630 }
3631 for obj, reviewer, reasons, mandatory, st in
3631 for obj, reviewer, reasons, mandatory, st in
3632 pull_request.reviewers_statuses()
3632 pull_request.reviewers_statuses()
3633 ]
3633 ]
3634 }
3634 }
3635
3635
3636 return data
3636 return data
3637
3637
3638
3638
3639 class PullRequest(Base, _PullRequestBase):
3639 class PullRequest(Base, _PullRequestBase):
3640 __tablename__ = 'pull_requests'
3640 __tablename__ = 'pull_requests'
3641 __table_args__ = (
3641 __table_args__ = (
3642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3644 )
3644 )
3645
3645
3646 pull_request_id = Column(
3646 pull_request_id = Column(
3647 'pull_request_id', Integer(), nullable=False, primary_key=True)
3647 'pull_request_id', Integer(), nullable=False, primary_key=True)
3648
3648
3649 def __repr__(self):
3649 def __repr__(self):
3650 if self.pull_request_id:
3650 if self.pull_request_id:
3651 return '<DB:PullRequest #%s>' % self.pull_request_id
3651 return '<DB:PullRequest #%s>' % self.pull_request_id
3652 else:
3652 else:
3653 return '<DB:PullRequest at %#x>' % id(self)
3653 return '<DB:PullRequest at %#x>' % id(self)
3654
3654
3655 reviewers = relationship('PullRequestReviewers',
3655 reviewers = relationship('PullRequestReviewers',
3656 cascade="all, delete, delete-orphan")
3656 cascade="all, delete, delete-orphan")
3657 statuses = relationship('ChangesetStatus',
3657 statuses = relationship('ChangesetStatus',
3658 cascade="all, delete, delete-orphan")
3658 cascade="all, delete, delete-orphan")
3659 comments = relationship('ChangesetComment',
3659 comments = relationship('ChangesetComment',
3660 cascade="all, delete, delete-orphan")
3660 cascade="all, delete, delete-orphan")
3661 versions = relationship('PullRequestVersion',
3661 versions = relationship('PullRequestVersion',
3662 cascade="all, delete, delete-orphan",
3662 cascade="all, delete, delete-orphan",
3663 lazy='dynamic')
3663 lazy='dynamic')
3664
3664
3665 @classmethod
3665 @classmethod
3666 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3666 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3667 internal_methods=None):
3667 internal_methods=None):
3668
3668
3669 class PullRequestDisplay(object):
3669 class PullRequestDisplay(object):
3670 """
3670 """
3671 Special object wrapper for showing PullRequest data via Versions
3671 Special object wrapper for showing PullRequest data via Versions
3672 It mimics PR object as close as possible. This is read only object
3672 It mimics PR object as close as possible. This is read only object
3673 just for display
3673 just for display
3674 """
3674 """
3675
3675
3676 def __init__(self, attrs, internal=None):
3676 def __init__(self, attrs, internal=None):
3677 self.attrs = attrs
3677 self.attrs = attrs
3678 # internal have priority over the given ones via attrs
3678 # internal have priority over the given ones via attrs
3679 self.internal = internal or ['versions']
3679 self.internal = internal or ['versions']
3680
3680
3681 def __getattr__(self, item):
3681 def __getattr__(self, item):
3682 if item in self.internal:
3682 if item in self.internal:
3683 return getattr(self, item)
3683 return getattr(self, item)
3684 try:
3684 try:
3685 return self.attrs[item]
3685 return self.attrs[item]
3686 except KeyError:
3686 except KeyError:
3687 raise AttributeError(
3687 raise AttributeError(
3688 '%s object has no attribute %s' % (self, item))
3688 '%s object has no attribute %s' % (self, item))
3689
3689
3690 def __repr__(self):
3690 def __repr__(self):
3691 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3691 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3692
3692
3693 def versions(self):
3693 def versions(self):
3694 return pull_request_obj.versions.order_by(
3694 return pull_request_obj.versions.order_by(
3695 PullRequestVersion.pull_request_version_id).all()
3695 PullRequestVersion.pull_request_version_id).all()
3696
3696
3697 def is_closed(self):
3697 def is_closed(self):
3698 return pull_request_obj.is_closed()
3698 return pull_request_obj.is_closed()
3699
3699
3700 @property
3700 @property
3701 def pull_request_version_id(self):
3701 def pull_request_version_id(self):
3702 return getattr(pull_request_obj, 'pull_request_version_id', None)
3702 return getattr(pull_request_obj, 'pull_request_version_id', None)
3703
3703
3704 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3704 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3705
3705
3706 attrs.author = StrictAttributeDict(
3706 attrs.author = StrictAttributeDict(
3707 pull_request_obj.author.get_api_data())
3707 pull_request_obj.author.get_api_data())
3708 if pull_request_obj.target_repo:
3708 if pull_request_obj.target_repo:
3709 attrs.target_repo = StrictAttributeDict(
3709 attrs.target_repo = StrictAttributeDict(
3710 pull_request_obj.target_repo.get_api_data())
3710 pull_request_obj.target_repo.get_api_data())
3711 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3711 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3712
3712
3713 if pull_request_obj.source_repo:
3713 if pull_request_obj.source_repo:
3714 attrs.source_repo = StrictAttributeDict(
3714 attrs.source_repo = StrictAttributeDict(
3715 pull_request_obj.source_repo.get_api_data())
3715 pull_request_obj.source_repo.get_api_data())
3716 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3716 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3717
3717
3718 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3718 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3719 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3719 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3720 attrs.revisions = pull_request_obj.revisions
3720 attrs.revisions = pull_request_obj.revisions
3721
3721
3722 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3722 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3723 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3723 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3724 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3724 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3725
3725
3726 return PullRequestDisplay(attrs, internal=internal_methods)
3726 return PullRequestDisplay(attrs, internal=internal_methods)
3727
3727
3728 def is_closed(self):
3728 def is_closed(self):
3729 return self.status == self.STATUS_CLOSED
3729 return self.status == self.STATUS_CLOSED
3730
3730
3731 def __json__(self):
3731 def __json__(self):
3732 return {
3732 return {
3733 'revisions': self.revisions,
3733 'revisions': self.revisions,
3734 }
3734 }
3735
3735
3736 def calculated_review_status(self):
3736 def calculated_review_status(self):
3737 from rhodecode.model.changeset_status import ChangesetStatusModel
3737 from rhodecode.model.changeset_status import ChangesetStatusModel
3738 return ChangesetStatusModel().calculated_review_status(self)
3738 return ChangesetStatusModel().calculated_review_status(self)
3739
3739
3740 def reviewers_statuses(self):
3740 def reviewers_statuses(self):
3741 from rhodecode.model.changeset_status import ChangesetStatusModel
3741 from rhodecode.model.changeset_status import ChangesetStatusModel
3742 return ChangesetStatusModel().reviewers_statuses(self)
3742 return ChangesetStatusModel().reviewers_statuses(self)
3743
3743
3744 @property
3744 @property
3745 def workspace_id(self):
3745 def workspace_id(self):
3746 from rhodecode.model.pull_request import PullRequestModel
3746 from rhodecode.model.pull_request import PullRequestModel
3747 return PullRequestModel()._workspace_id(self)
3747 return PullRequestModel()._workspace_id(self)
3748
3748
3749 def get_shadow_repo(self):
3749 def get_shadow_repo(self):
3750 workspace_id = self.workspace_id
3750 workspace_id = self.workspace_id
3751 vcs_obj = self.target_repo.scm_instance()
3751 vcs_obj = self.target_repo.scm_instance()
3752 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3752 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3753 workspace_id)
3753 workspace_id)
3754 return vcs_obj._get_shadow_instance(shadow_repository_path)
3754 return vcs_obj._get_shadow_instance(shadow_repository_path)
3755
3755
3756
3756
3757 class PullRequestVersion(Base, _PullRequestBase):
3757 class PullRequestVersion(Base, _PullRequestBase):
3758 __tablename__ = 'pull_request_versions'
3758 __tablename__ = 'pull_request_versions'
3759 __table_args__ = (
3759 __table_args__ = (
3760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3761 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3761 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3762 )
3762 )
3763
3763
3764 pull_request_version_id = Column(
3764 pull_request_version_id = Column(
3765 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3765 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3766 pull_request_id = Column(
3766 pull_request_id = Column(
3767 'pull_request_id', Integer(),
3767 'pull_request_id', Integer(),
3768 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3768 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3769 pull_request = relationship('PullRequest')
3769 pull_request = relationship('PullRequest')
3770
3770
3771 def __repr__(self):
3771 def __repr__(self):
3772 if self.pull_request_version_id:
3772 if self.pull_request_version_id:
3773 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3773 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3774 else:
3774 else:
3775 return '<DB:PullRequestVersion at %#x>' % id(self)
3775 return '<DB:PullRequestVersion at %#x>' % id(self)
3776
3776
3777 @property
3777 @property
3778 def reviewers(self):
3778 def reviewers(self):
3779 return self.pull_request.reviewers
3779 return self.pull_request.reviewers
3780
3780
3781 @property
3781 @property
3782 def versions(self):
3782 def versions(self):
3783 return self.pull_request.versions
3783 return self.pull_request.versions
3784
3784
3785 def is_closed(self):
3785 def is_closed(self):
3786 # calculate from original
3786 # calculate from original
3787 return self.pull_request.status == self.STATUS_CLOSED
3787 return self.pull_request.status == self.STATUS_CLOSED
3788
3788
3789 def calculated_review_status(self):
3789 def calculated_review_status(self):
3790 return self.pull_request.calculated_review_status()
3790 return self.pull_request.calculated_review_status()
3791
3791
3792 def reviewers_statuses(self):
3792 def reviewers_statuses(self):
3793 return self.pull_request.reviewers_statuses()
3793 return self.pull_request.reviewers_statuses()
3794
3794
3795
3795
3796 class PullRequestReviewers(Base, BaseModel):
3796 class PullRequestReviewers(Base, BaseModel):
3797 __tablename__ = 'pull_request_reviewers'
3797 __tablename__ = 'pull_request_reviewers'
3798 __table_args__ = (
3798 __table_args__ = (
3799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3801 )
3801 )
3802
3802
3803 @hybrid_property
3803 @hybrid_property
3804 def reasons(self):
3804 def reasons(self):
3805 if not self._reasons:
3805 if not self._reasons:
3806 return []
3806 return []
3807 return self._reasons
3807 return self._reasons
3808
3808
3809 @reasons.setter
3809 @reasons.setter
3810 def reasons(self, val):
3810 def reasons(self, val):
3811 val = val or []
3811 val = val or []
3812 if any(not isinstance(x, basestring) for x in val):
3812 if any(not isinstance(x, basestring) for x in val):
3813 raise Exception('invalid reasons type, must be list of strings')
3813 raise Exception('invalid reasons type, must be list of strings')
3814 self._reasons = val
3814 self._reasons = val
3815
3815
3816 pull_requests_reviewers_id = Column(
3816 pull_requests_reviewers_id = Column(
3817 'pull_requests_reviewers_id', Integer(), nullable=False,
3817 'pull_requests_reviewers_id', Integer(), nullable=False,
3818 primary_key=True)
3818 primary_key=True)
3819 pull_request_id = Column(
3819 pull_request_id = Column(
3820 "pull_request_id", Integer(),
3820 "pull_request_id", Integer(),
3821 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3821 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3822 user_id = Column(
3822 user_id = Column(
3823 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3823 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3824 _reasons = Column(
3824 _reasons = Column(
3825 'reason', MutationList.as_mutable(
3825 'reason', MutationList.as_mutable(
3826 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3826 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3827
3827
3828 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3828 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3829 user = relationship('User')
3829 user = relationship('User')
3830 pull_request = relationship('PullRequest')
3830 pull_request = relationship('PullRequest')
3831
3831
3832 rule_data = Column(
3832 rule_data = Column(
3833 'rule_data_json',
3833 'rule_data_json',
3834 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3834 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3835
3835
3836 def rule_user_group_data(self):
3836 def rule_user_group_data(self):
3837 """
3837 """
3838 Returns the voting user group rule data for this reviewer
3838 Returns the voting user group rule data for this reviewer
3839 """
3839 """
3840
3840
3841 if self.rule_data and 'vote_rule' in self.rule_data:
3841 if self.rule_data and 'vote_rule' in self.rule_data:
3842 user_group_data = {}
3842 user_group_data = {}
3843 if 'rule_user_group_entry_id' in self.rule_data:
3843 if 'rule_user_group_entry_id' in self.rule_data:
3844 # means a group with voting rules !
3844 # means a group with voting rules !
3845 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3845 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3846 user_group_data['name'] = self.rule_data['rule_name']
3846 user_group_data['name'] = self.rule_data['rule_name']
3847 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3847 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3848
3848
3849 return user_group_data
3849 return user_group_data
3850
3850
3851 def __unicode__(self):
3851 def __unicode__(self):
3852 return u"<%s('id:%s')>" % (self.__class__.__name__,
3852 return u"<%s('id:%s')>" % (self.__class__.__name__,
3853 self.pull_requests_reviewers_id)
3853 self.pull_requests_reviewers_id)
3854
3854
3855
3855
3856 class Notification(Base, BaseModel):
3856 class Notification(Base, BaseModel):
3857 __tablename__ = 'notifications'
3857 __tablename__ = 'notifications'
3858 __table_args__ = (
3858 __table_args__ = (
3859 Index('notification_type_idx', 'type'),
3859 Index('notification_type_idx', 'type'),
3860 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3860 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3861 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3861 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3862 )
3862 )
3863
3863
3864 TYPE_CHANGESET_COMMENT = u'cs_comment'
3864 TYPE_CHANGESET_COMMENT = u'cs_comment'
3865 TYPE_MESSAGE = u'message'
3865 TYPE_MESSAGE = u'message'
3866 TYPE_MENTION = u'mention'
3866 TYPE_MENTION = u'mention'
3867 TYPE_REGISTRATION = u'registration'
3867 TYPE_REGISTRATION = u'registration'
3868 TYPE_PULL_REQUEST = u'pull_request'
3868 TYPE_PULL_REQUEST = u'pull_request'
3869 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3869 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3870
3870
3871 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3871 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3872 subject = Column('subject', Unicode(512), nullable=True)
3872 subject = Column('subject', Unicode(512), nullable=True)
3873 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3873 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3874 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3874 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3875 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3875 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3876 type_ = Column('type', Unicode(255))
3876 type_ = Column('type', Unicode(255))
3877
3877
3878 created_by_user = relationship('User')
3878 created_by_user = relationship('User')
3879 notifications_to_users = relationship('UserNotification', lazy='joined',
3879 notifications_to_users = relationship('UserNotification', lazy='joined',
3880 cascade="all, delete, delete-orphan")
3880 cascade="all, delete, delete-orphan")
3881
3881
3882 @property
3882 @property
3883 def recipients(self):
3883 def recipients(self):
3884 return [x.user for x in UserNotification.query()\
3884 return [x.user for x in UserNotification.query()\
3885 .filter(UserNotification.notification == self)\
3885 .filter(UserNotification.notification == self)\
3886 .order_by(UserNotification.user_id.asc()).all()]
3886 .order_by(UserNotification.user_id.asc()).all()]
3887
3887
3888 @classmethod
3888 @classmethod
3889 def create(cls, created_by, subject, body, recipients, type_=None):
3889 def create(cls, created_by, subject, body, recipients, type_=None):
3890 if type_ is None:
3890 if type_ is None:
3891 type_ = Notification.TYPE_MESSAGE
3891 type_ = Notification.TYPE_MESSAGE
3892
3892
3893 notification = cls()
3893 notification = cls()
3894 notification.created_by_user = created_by
3894 notification.created_by_user = created_by
3895 notification.subject = subject
3895 notification.subject = subject
3896 notification.body = body
3896 notification.body = body
3897 notification.type_ = type_
3897 notification.type_ = type_
3898 notification.created_on = datetime.datetime.now()
3898 notification.created_on = datetime.datetime.now()
3899
3899
3900 for u in recipients:
3900 for u in recipients:
3901 assoc = UserNotification()
3901 assoc = UserNotification()
3902 assoc.notification = notification
3902 assoc.notification = notification
3903
3903
3904 # if created_by is inside recipients mark his notification
3904 # if created_by is inside recipients mark his notification
3905 # as read
3905 # as read
3906 if u.user_id == created_by.user_id:
3906 if u.user_id == created_by.user_id:
3907 assoc.read = True
3907 assoc.read = True
3908
3908
3909 u.notifications.append(assoc)
3909 u.notifications.append(assoc)
3910 Session().add(notification)
3910 Session().add(notification)
3911
3911
3912 return notification
3912 return notification
3913
3913
3914
3914
3915 class UserNotification(Base, BaseModel):
3915 class UserNotification(Base, BaseModel):
3916 __tablename__ = 'user_to_notification'
3916 __tablename__ = 'user_to_notification'
3917 __table_args__ = (
3917 __table_args__ = (
3918 UniqueConstraint('user_id', 'notification_id'),
3918 UniqueConstraint('user_id', 'notification_id'),
3919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3921 )
3921 )
3922 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3922 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3923 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3923 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3924 read = Column('read', Boolean, default=False)
3924 read = Column('read', Boolean, default=False)
3925 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3925 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3926
3926
3927 user = relationship('User', lazy="joined")
3927 user = relationship('User', lazy="joined")
3928 notification = relationship('Notification', lazy="joined",
3928 notification = relationship('Notification', lazy="joined",
3929 order_by=lambda: Notification.created_on.desc(),)
3929 order_by=lambda: Notification.created_on.desc(),)
3930
3930
3931 def mark_as_read(self):
3931 def mark_as_read(self):
3932 self.read = True
3932 self.read = True
3933 Session().add(self)
3933 Session().add(self)
3934
3934
3935
3935
3936 class Gist(Base, BaseModel):
3936 class Gist(Base, BaseModel):
3937 __tablename__ = 'gists'
3937 __tablename__ = 'gists'
3938 __table_args__ = (
3938 __table_args__ = (
3939 Index('g_gist_access_id_idx', 'gist_access_id'),
3939 Index('g_gist_access_id_idx', 'gist_access_id'),
3940 Index('g_created_on_idx', 'created_on'),
3940 Index('g_created_on_idx', 'created_on'),
3941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3943 )
3943 )
3944 GIST_PUBLIC = u'public'
3944 GIST_PUBLIC = u'public'
3945 GIST_PRIVATE = u'private'
3945 GIST_PRIVATE = u'private'
3946 DEFAULT_FILENAME = u'gistfile1.txt'
3946 DEFAULT_FILENAME = u'gistfile1.txt'
3947
3947
3948 ACL_LEVEL_PUBLIC = u'acl_public'
3948 ACL_LEVEL_PUBLIC = u'acl_public'
3949 ACL_LEVEL_PRIVATE = u'acl_private'
3949 ACL_LEVEL_PRIVATE = u'acl_private'
3950
3950
3951 gist_id = Column('gist_id', Integer(), primary_key=True)
3951 gist_id = Column('gist_id', Integer(), primary_key=True)
3952 gist_access_id = Column('gist_access_id', Unicode(250))
3952 gist_access_id = Column('gist_access_id', Unicode(250))
3953 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3953 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3954 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3954 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3955 gist_expires = Column('gist_expires', Float(53), nullable=False)
3955 gist_expires = Column('gist_expires', Float(53), nullable=False)
3956 gist_type = Column('gist_type', Unicode(128), nullable=False)
3956 gist_type = Column('gist_type', Unicode(128), nullable=False)
3957 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3957 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3958 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3958 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3959 acl_level = Column('acl_level', Unicode(128), nullable=True)
3959 acl_level = Column('acl_level', Unicode(128), nullable=True)
3960
3960
3961 owner = relationship('User')
3961 owner = relationship('User')
3962
3962
3963 def __repr__(self):
3963 def __repr__(self):
3964 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3964 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3965
3965
3966 @hybrid_property
3966 @hybrid_property
3967 def description_safe(self):
3967 def description_safe(self):
3968 from rhodecode.lib import helpers as h
3968 from rhodecode.lib import helpers as h
3969 return h.escape(self.gist_description)
3969 return h.escape(self.gist_description)
3970
3970
3971 @classmethod
3971 @classmethod
3972 def get_or_404(cls, id_):
3972 def get_or_404(cls, id_):
3973 from pyramid.httpexceptions import HTTPNotFound
3973 from pyramid.httpexceptions import HTTPNotFound
3974
3974
3975 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3975 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3976 if not res:
3976 if not res:
3977 raise HTTPNotFound()
3977 raise HTTPNotFound()
3978 return res
3978 return res
3979
3979
3980 @classmethod
3980 @classmethod
3981 def get_by_access_id(cls, gist_access_id):
3981 def get_by_access_id(cls, gist_access_id):
3982 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3982 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3983
3983
3984 def gist_url(self):
3984 def gist_url(self):
3985 from rhodecode.model.gist import GistModel
3985 from rhodecode.model.gist import GistModel
3986 return GistModel().get_url(self)
3986 return GistModel().get_url(self)
3987
3987
3988 @classmethod
3988 @classmethod
3989 def base_path(cls):
3989 def base_path(cls):
3990 """
3990 """
3991 Returns base path when all gists are stored
3991 Returns base path when all gists are stored
3992
3992
3993 :param cls:
3993 :param cls:
3994 """
3994 """
3995 from rhodecode.model.gist import GIST_STORE_LOC
3995 from rhodecode.model.gist import GIST_STORE_LOC
3996 q = Session().query(RhodeCodeUi)\
3996 q = Session().query(RhodeCodeUi)\
3997 .filter(RhodeCodeUi.ui_key == URL_SEP)
3997 .filter(RhodeCodeUi.ui_key == URL_SEP)
3998 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3998 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3999 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3999 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4000
4000
4001 def get_api_data(self):
4001 def get_api_data(self):
4002 """
4002 """
4003 Common function for generating gist related data for API
4003 Common function for generating gist related data for API
4004 """
4004 """
4005 gist = self
4005 gist = self
4006 data = {
4006 data = {
4007 'gist_id': gist.gist_id,
4007 'gist_id': gist.gist_id,
4008 'type': gist.gist_type,
4008 'type': gist.gist_type,
4009 'access_id': gist.gist_access_id,
4009 'access_id': gist.gist_access_id,
4010 'description': gist.gist_description,
4010 'description': gist.gist_description,
4011 'url': gist.gist_url(),
4011 'url': gist.gist_url(),
4012 'expires': gist.gist_expires,
4012 'expires': gist.gist_expires,
4013 'created_on': gist.created_on,
4013 'created_on': gist.created_on,
4014 'modified_at': gist.modified_at,
4014 'modified_at': gist.modified_at,
4015 'content': None,
4015 'content': None,
4016 'acl_level': gist.acl_level,
4016 'acl_level': gist.acl_level,
4017 }
4017 }
4018 return data
4018 return data
4019
4019
4020 def __json__(self):
4020 def __json__(self):
4021 data = dict(
4021 data = dict(
4022 )
4022 )
4023 data.update(self.get_api_data())
4023 data.update(self.get_api_data())
4024 return data
4024 return data
4025 # SCM functions
4025 # SCM functions
4026
4026
4027 def scm_instance(self, **kwargs):
4027 def scm_instance(self, **kwargs):
4028 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4028 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4029 return get_vcs_instance(
4029 return get_vcs_instance(
4030 repo_path=safe_str(full_repo_path), create=False)
4030 repo_path=safe_str(full_repo_path), create=False)
4031
4031
4032
4032
4033 class ExternalIdentity(Base, BaseModel):
4033 class ExternalIdentity(Base, BaseModel):
4034 __tablename__ = 'external_identities'
4034 __tablename__ = 'external_identities'
4035 __table_args__ = (
4035 __table_args__ = (
4036 Index('local_user_id_idx', 'local_user_id'),
4036 Index('local_user_id_idx', 'local_user_id'),
4037 Index('external_id_idx', 'external_id'),
4037 Index('external_id_idx', 'external_id'),
4038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4039 'mysql_charset': 'utf8'})
4039 'mysql_charset': 'utf8'})
4040
4040
4041 external_id = Column('external_id', Unicode(255), default=u'',
4041 external_id = Column('external_id', Unicode(255), default=u'',
4042 primary_key=True)
4042 primary_key=True)
4043 external_username = Column('external_username', Unicode(1024), default=u'')
4043 external_username = Column('external_username', Unicode(1024), default=u'')
4044 local_user_id = Column('local_user_id', Integer(),
4044 local_user_id = Column('local_user_id', Integer(),
4045 ForeignKey('users.user_id'), primary_key=True)
4045 ForeignKey('users.user_id'), primary_key=True)
4046 provider_name = Column('provider_name', Unicode(255), default=u'',
4046 provider_name = Column('provider_name', Unicode(255), default=u'',
4047 primary_key=True)
4047 primary_key=True)
4048 access_token = Column('access_token', String(1024), default=u'')
4048 access_token = Column('access_token', String(1024), default=u'')
4049 alt_token = Column('alt_token', String(1024), default=u'')
4049 alt_token = Column('alt_token', String(1024), default=u'')
4050 token_secret = Column('token_secret', String(1024), default=u'')
4050 token_secret = Column('token_secret', String(1024), default=u'')
4051
4051
4052 @classmethod
4052 @classmethod
4053 def by_external_id_and_provider(cls, external_id, provider_name,
4053 def by_external_id_and_provider(cls, external_id, provider_name,
4054 local_user_id=None):
4054 local_user_id=None):
4055 """
4055 """
4056 Returns ExternalIdentity instance based on search params
4056 Returns ExternalIdentity instance based on search params
4057
4057
4058 :param external_id:
4058 :param external_id:
4059 :param provider_name:
4059 :param provider_name:
4060 :return: ExternalIdentity
4060 :return: ExternalIdentity
4061 """
4061 """
4062 query = cls.query()
4062 query = cls.query()
4063 query = query.filter(cls.external_id == external_id)
4063 query = query.filter(cls.external_id == external_id)
4064 query = query.filter(cls.provider_name == provider_name)
4064 query = query.filter(cls.provider_name == provider_name)
4065 if local_user_id:
4065 if local_user_id:
4066 query = query.filter(cls.local_user_id == local_user_id)
4066 query = query.filter(cls.local_user_id == local_user_id)
4067 return query.first()
4067 return query.first()
4068
4068
4069 @classmethod
4069 @classmethod
4070 def user_by_external_id_and_provider(cls, external_id, provider_name):
4070 def user_by_external_id_and_provider(cls, external_id, provider_name):
4071 """
4071 """
4072 Returns User instance based on search params
4072 Returns User instance based on search params
4073
4073
4074 :param external_id:
4074 :param external_id:
4075 :param provider_name:
4075 :param provider_name:
4076 :return: User
4076 :return: User
4077 """
4077 """
4078 query = User.query()
4078 query = User.query()
4079 query = query.filter(cls.external_id == external_id)
4079 query = query.filter(cls.external_id == external_id)
4080 query = query.filter(cls.provider_name == provider_name)
4080 query = query.filter(cls.provider_name == provider_name)
4081 query = query.filter(User.user_id == cls.local_user_id)
4081 query = query.filter(User.user_id == cls.local_user_id)
4082 return query.first()
4082 return query.first()
4083
4083
4084 @classmethod
4084 @classmethod
4085 def by_local_user_id(cls, local_user_id):
4085 def by_local_user_id(cls, local_user_id):
4086 """
4086 """
4087 Returns all tokens for user
4087 Returns all tokens for user
4088
4088
4089 :param local_user_id:
4089 :param local_user_id:
4090 :return: ExternalIdentity
4090 :return: ExternalIdentity
4091 """
4091 """
4092 query = cls.query()
4092 query = cls.query()
4093 query = query.filter(cls.local_user_id == local_user_id)
4093 query = query.filter(cls.local_user_id == local_user_id)
4094 return query
4094 return query
4095
4095
4096
4096
4097 class Integration(Base, BaseModel):
4097 class Integration(Base, BaseModel):
4098 __tablename__ = 'integrations'
4098 __tablename__ = 'integrations'
4099 __table_args__ = (
4099 __table_args__ = (
4100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4102 )
4102 )
4103
4103
4104 integration_id = Column('integration_id', Integer(), primary_key=True)
4104 integration_id = Column('integration_id', Integer(), primary_key=True)
4105 integration_type = Column('integration_type', String(255))
4105 integration_type = Column('integration_type', String(255))
4106 enabled = Column('enabled', Boolean(), nullable=False)
4106 enabled = Column('enabled', Boolean(), nullable=False)
4107 name = Column('name', String(255), nullable=False)
4107 name = Column('name', String(255), nullable=False)
4108 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4108 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4109 default=False)
4109 default=False)
4110
4110
4111 settings = Column(
4111 settings = Column(
4112 'settings_json', MutationObj.as_mutable(
4112 'settings_json', MutationObj.as_mutable(
4113 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4113 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4114 repo_id = Column(
4114 repo_id = Column(
4115 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4115 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4116 nullable=True, unique=None, default=None)
4116 nullable=True, unique=None, default=None)
4117 repo = relationship('Repository', lazy='joined')
4117 repo = relationship('Repository', lazy='joined')
4118
4118
4119 repo_group_id = Column(
4119 repo_group_id = Column(
4120 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4120 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4121 nullable=True, unique=None, default=None)
4121 nullable=True, unique=None, default=None)
4122 repo_group = relationship('RepoGroup', lazy='joined')
4122 repo_group = relationship('RepoGroup', lazy='joined')
4123
4123
4124 @property
4124 @property
4125 def scope(self):
4125 def scope(self):
4126 if self.repo:
4126 if self.repo:
4127 return repr(self.repo)
4127 return repr(self.repo)
4128 if self.repo_group:
4128 if self.repo_group:
4129 if self.child_repos_only:
4129 if self.child_repos_only:
4130 return repr(self.repo_group) + ' (child repos only)'
4130 return repr(self.repo_group) + ' (child repos only)'
4131 else:
4131 else:
4132 return repr(self.repo_group) + ' (recursive)'
4132 return repr(self.repo_group) + ' (recursive)'
4133 if self.child_repos_only:
4133 if self.child_repos_only:
4134 return 'root_repos'
4134 return 'root_repos'
4135 return 'global'
4135 return 'global'
4136
4136
4137 def __repr__(self):
4137 def __repr__(self):
4138 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4138 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4139
4139
4140
4140
4141 class RepoReviewRuleUser(Base, BaseModel):
4141 class RepoReviewRuleUser(Base, BaseModel):
4142 __tablename__ = 'repo_review_rules_users'
4142 __tablename__ = 'repo_review_rules_users'
4143 __table_args__ = (
4143 __table_args__ = (
4144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4145 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4145 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4146 )
4146 )
4147
4147
4148 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4148 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4149 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4149 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4150 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4150 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4151 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4151 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4152 user = relationship('User')
4152 user = relationship('User')
4153
4153
4154 def rule_data(self):
4154 def rule_data(self):
4155 return {
4155 return {
4156 'mandatory': self.mandatory
4156 'mandatory': self.mandatory
4157 }
4157 }
4158
4158
4159
4159
4160 class RepoReviewRuleUserGroup(Base, BaseModel):
4160 class RepoReviewRuleUserGroup(Base, BaseModel):
4161 __tablename__ = 'repo_review_rules_users_groups'
4161 __tablename__ = 'repo_review_rules_users_groups'
4162 __table_args__ = (
4162 __table_args__ = (
4163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4165 )
4165 )
4166 VOTE_RULE_ALL = -1
4166 VOTE_RULE_ALL = -1
4167
4167
4168 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4168 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4169 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4169 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4170 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4170 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4171 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4171 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4172 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4172 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4173 users_group = relationship('UserGroup')
4173 users_group = relationship('UserGroup')
4174
4174
4175 def rule_data(self):
4175 def rule_data(self):
4176 return {
4176 return {
4177 'mandatory': self.mandatory,
4177 'mandatory': self.mandatory,
4178 'vote_rule': self.vote_rule
4178 'vote_rule': self.vote_rule
4179 }
4179 }
4180
4180
4181 @property
4181 @property
4182 def vote_rule_label(self):
4182 def vote_rule_label(self):
4183 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4183 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4184 return 'all must vote'
4184 return 'all must vote'
4185 else:
4185 else:
4186 return 'min. vote {}'.format(self.vote_rule)
4186 return 'min. vote {}'.format(self.vote_rule)
4187
4187
4188
4188
4189 class RepoReviewRule(Base, BaseModel):
4189 class RepoReviewRule(Base, BaseModel):
4190 __tablename__ = 'repo_review_rules'
4190 __tablename__ = 'repo_review_rules'
4191 __table_args__ = (
4191 __table_args__ = (
4192 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4192 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4193 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4193 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4194 )
4194 )
4195
4195
4196 repo_review_rule_id = Column(
4196 repo_review_rule_id = Column(
4197 'repo_review_rule_id', Integer(), primary_key=True)
4197 'repo_review_rule_id', Integer(), primary_key=True)
4198 repo_id = Column(
4198 repo_id = Column(
4199 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4199 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4200 repo = relationship('Repository', backref='review_rules')
4200 repo = relationship('Repository', backref='review_rules')
4201
4201
4202 review_rule_name = Column('review_rule_name', String(255))
4202 review_rule_name = Column('review_rule_name', String(255))
4203 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4203 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4204 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4204 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4205 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4205 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4206
4206
4207 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4207 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4208 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4208 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4209 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4209 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4210 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4210 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4211
4211
4212 rule_users = relationship('RepoReviewRuleUser')
4212 rule_users = relationship('RepoReviewRuleUser')
4213 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4213 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4214
4214
4215 def _validate_glob(self, value):
4215 def _validate_glob(self, value):
4216 re.compile('^' + glob2re(value) + '$')
4216 re.compile('^' + glob2re(value) + '$')
4217
4217
4218 @hybrid_property
4218 @hybrid_property
4219 def source_branch_pattern(self):
4219 def source_branch_pattern(self):
4220 return self._branch_pattern or '*'
4220 return self._branch_pattern or '*'
4221
4221
4222 @source_branch_pattern.setter
4222 @source_branch_pattern.setter
4223 def source_branch_pattern(self, value):
4223 def source_branch_pattern(self, value):
4224 self._validate_glob(value)
4224 self._validate_glob(value)
4225 self._branch_pattern = value or '*'
4225 self._branch_pattern = value or '*'
4226
4226
4227 @hybrid_property
4227 @hybrid_property
4228 def target_branch_pattern(self):
4228 def target_branch_pattern(self):
4229 return self._target_branch_pattern or '*'
4229 return self._target_branch_pattern or '*'
4230
4230
4231 @target_branch_pattern.setter
4231 @target_branch_pattern.setter
4232 def target_branch_pattern(self, value):
4232 def target_branch_pattern(self, value):
4233 self._validate_glob(value)
4233 self._validate_glob(value)
4234 self._target_branch_pattern = value or '*'
4234 self._target_branch_pattern = value or '*'
4235
4235
4236 @hybrid_property
4236 @hybrid_property
4237 def file_pattern(self):
4237 def file_pattern(self):
4238 return self._file_pattern or '*'
4238 return self._file_pattern or '*'
4239
4239
4240 @file_pattern.setter
4240 @file_pattern.setter
4241 def file_pattern(self, value):
4241 def file_pattern(self, value):
4242 self._validate_glob(value)
4242 self._validate_glob(value)
4243 self._file_pattern = value or '*'
4243 self._file_pattern = value or '*'
4244
4244
4245 def matches(self, source_branch, target_branch, files_changed):
4245 def matches(self, source_branch, target_branch, files_changed):
4246 """
4246 """
4247 Check if this review rule matches a branch/files in a pull request
4247 Check if this review rule matches a branch/files in a pull request
4248
4248
4249 :param source_branch: source branch name for the commit
4249 :param source_branch: source branch name for the commit
4250 :param target_branch: target branch name for the commit
4250 :param target_branch: target branch name for the commit
4251 :param files_changed: list of file paths changed in the pull request
4251 :param files_changed: list of file paths changed in the pull request
4252 """
4252 """
4253
4253
4254 source_branch = source_branch or ''
4254 source_branch = source_branch or ''
4255 target_branch = target_branch or ''
4255 target_branch = target_branch or ''
4256 files_changed = files_changed or []
4256 files_changed = files_changed or []
4257
4257
4258 branch_matches = True
4258 branch_matches = True
4259 if source_branch or target_branch:
4259 if source_branch or target_branch:
4260 if self.source_branch_pattern == '*':
4260 if self.source_branch_pattern == '*':
4261 source_branch_match = True
4261 source_branch_match = True
4262 else:
4262 else:
4263 source_branch_regex = re.compile(
4263 source_branch_regex = re.compile(
4264 '^' + glob2re(self.source_branch_pattern) + '$')
4264 '^' + glob2re(self.source_branch_pattern) + '$')
4265 source_branch_match = bool(source_branch_regex.search(source_branch))
4265 source_branch_match = bool(source_branch_regex.search(source_branch))
4266 if self.target_branch_pattern == '*':
4266 if self.target_branch_pattern == '*':
4267 target_branch_match = True
4267 target_branch_match = True
4268 else:
4268 else:
4269 target_branch_regex = re.compile(
4269 target_branch_regex = re.compile(
4270 '^' + glob2re(self.target_branch_pattern) + '$')
4270 '^' + glob2re(self.target_branch_pattern) + '$')
4271 target_branch_match = bool(target_branch_regex.search(target_branch))
4271 target_branch_match = bool(target_branch_regex.search(target_branch))
4272
4272
4273 branch_matches = source_branch_match and target_branch_match
4273 branch_matches = source_branch_match and target_branch_match
4274
4274
4275 files_matches = True
4275 files_matches = True
4276 if self.file_pattern != '*':
4276 if self.file_pattern != '*':
4277 files_matches = False
4277 files_matches = False
4278 file_regex = re.compile(glob2re(self.file_pattern))
4278 file_regex = re.compile(glob2re(self.file_pattern))
4279 for filename in files_changed:
4279 for filename in files_changed:
4280 if file_regex.search(filename):
4280 if file_regex.search(filename):
4281 files_matches = True
4281 files_matches = True
4282 break
4282 break
4283
4283
4284 return branch_matches and files_matches
4284 return branch_matches and files_matches
4285
4285
4286 @property
4286 @property
4287 def review_users(self):
4287 def review_users(self):
4288 """ Returns the users which this rule applies to """
4288 """ Returns the users which this rule applies to """
4289
4289
4290 users = collections.OrderedDict()
4290 users = collections.OrderedDict()
4291
4291
4292 for rule_user in self.rule_users:
4292 for rule_user in self.rule_users:
4293 if rule_user.user.active:
4293 if rule_user.user.active:
4294 if rule_user.user not in users:
4294 if rule_user.user not in users:
4295 users[rule_user.user.username] = {
4295 users[rule_user.user.username] = {
4296 'user': rule_user.user,
4296 'user': rule_user.user,
4297 'source': 'user',
4297 'source': 'user',
4298 'source_data': {},
4298 'source_data': {},
4299 'data': rule_user.rule_data()
4299 'data': rule_user.rule_data()
4300 }
4300 }
4301
4301
4302 for rule_user_group in self.rule_user_groups:
4302 for rule_user_group in self.rule_user_groups:
4303 source_data = {
4303 source_data = {
4304 'user_group_id': rule_user_group.users_group.users_group_id,
4304 'user_group_id': rule_user_group.users_group.users_group_id,
4305 'name': rule_user_group.users_group.users_group_name,
4305 'name': rule_user_group.users_group.users_group_name,
4306 'members': len(rule_user_group.users_group.members)
4306 'members': len(rule_user_group.users_group.members)
4307 }
4307 }
4308 for member in rule_user_group.users_group.members:
4308 for member in rule_user_group.users_group.members:
4309 if member.user.active:
4309 if member.user.active:
4310 key = member.user.username
4310 key = member.user.username
4311 if key in users:
4311 if key in users:
4312 # skip this member as we have him already
4312 # skip this member as we have him already
4313 # this prevents from override the "first" matched
4313 # this prevents from override the "first" matched
4314 # users with duplicates in multiple groups
4314 # users with duplicates in multiple groups
4315 continue
4315 continue
4316
4316
4317 users[key] = {
4317 users[key] = {
4318 'user': member.user,
4318 'user': member.user,
4319 'source': 'user_group',
4319 'source': 'user_group',
4320 'source_data': source_data,
4320 'source_data': source_data,
4321 'data': rule_user_group.rule_data()
4321 'data': rule_user_group.rule_data()
4322 }
4322 }
4323
4323
4324 return users
4324 return users
4325
4325
4326 def user_group_vote_rule(self):
4326 def user_group_vote_rule(self):
4327 rules = []
4327 rules = []
4328 if self.rule_user_groups:
4328 if self.rule_user_groups:
4329 for user_group in self.rule_user_groups:
4329 for user_group in self.rule_user_groups:
4330 rules.append(user_group)
4330 rules.append(user_group)
4331 return rules
4331 return rules
4332
4332
4333 def __repr__(self):
4333 def __repr__(self):
4334 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4334 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4335 self.repo_review_rule_id, self.repo)
4335 self.repo_review_rule_id, self.repo)
4336
4336
4337
4337
4338 class ScheduleEntry(Base, BaseModel):
4338 class ScheduleEntry(Base, BaseModel):
4339 __tablename__ = 'schedule_entries'
4339 __tablename__ = 'schedule_entries'
4340 __table_args__ = (
4340 __table_args__ = (
4341 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4341 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4342 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4342 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4344 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4344 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4345 )
4345 )
4346 schedule_types = ['crontab', 'timedelta', 'integer']
4346 schedule_types = ['crontab', 'timedelta', 'integer']
4347 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4347 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4348
4348
4349 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4349 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4350 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4350 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4351 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4351 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4352
4352
4353 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4353 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4354 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4354 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4355
4355
4356 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4356 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4357 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4357 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4358
4358
4359 # task
4359 # task
4360 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4360 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4361 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4361 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4362 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4362 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4363 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4363 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4364
4364
4365 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4365 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4366 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4366 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4367
4367
4368 @hybrid_property
4368 @hybrid_property
4369 def schedule_type(self):
4369 def schedule_type(self):
4370 return self._schedule_type
4370 return self._schedule_type
4371
4371
4372 @schedule_type.setter
4372 @schedule_type.setter
4373 def schedule_type(self, val):
4373 def schedule_type(self, val):
4374 if val not in self.schedule_types:
4374 if val not in self.schedule_types:
4375 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4375 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4376 val, self.schedule_type))
4376 val, self.schedule_type))
4377
4377
4378 self._schedule_type = val
4378 self._schedule_type = val
4379
4379
4380 @classmethod
4380 @classmethod
4381 def get_uid(cls, obj):
4381 def get_uid(cls, obj):
4382 args = obj.task_args
4382 args = obj.task_args
4383 kwargs = obj.task_kwargs
4383 kwargs = obj.task_kwargs
4384 if isinstance(args, JsonRaw):
4384 if isinstance(args, JsonRaw):
4385 try:
4385 try:
4386 args = json.loads(args)
4386 args = json.loads(args)
4387 except ValueError:
4387 except ValueError:
4388 args = tuple()
4388 args = tuple()
4389
4389
4390 if isinstance(kwargs, JsonRaw):
4390 if isinstance(kwargs, JsonRaw):
4391 try:
4391 try:
4392 kwargs = json.loads(kwargs)
4392 kwargs = json.loads(kwargs)
4393 except ValueError:
4393 except ValueError:
4394 kwargs = dict()
4394 kwargs = dict()
4395
4395
4396 dot_notation = obj.task_dot_notation
4396 dot_notation = obj.task_dot_notation
4397 val = '.'.join(map(safe_str, [
4397 val = '.'.join(map(safe_str, [
4398 sorted(dot_notation), args, sorted(kwargs.items())]))
4398 sorted(dot_notation), args, sorted(kwargs.items())]))
4399 return hashlib.sha1(val).hexdigest()
4399 return hashlib.sha1(val).hexdigest()
4400
4400
4401 @classmethod
4401 @classmethod
4402 def get_by_schedule_name(cls, schedule_name):
4402 def get_by_schedule_name(cls, schedule_name):
4403 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4403 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4404
4404
4405 @classmethod
4405 @classmethod
4406 def get_by_schedule_id(cls, schedule_id):
4406 def get_by_schedule_id(cls, schedule_id):
4407 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4407 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4408
4408
4409 @property
4409 @property
4410 def task(self):
4410 def task(self):
4411 return self.task_dot_notation
4411 return self.task_dot_notation
4412
4412
4413 @property
4413 @property
4414 def schedule(self):
4414 def schedule(self):
4415 from rhodecode.lib.celerylib.utils import raw_2_schedule
4415 from rhodecode.lib.celerylib.utils import raw_2_schedule
4416 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4416 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4417 return schedule
4417 return schedule
4418
4418
4419 @property
4419 @property
4420 def args(self):
4420 def args(self):
4421 try:
4421 try:
4422 return list(self.task_args or [])
4422 return list(self.task_args or [])
4423 except ValueError:
4423 except ValueError:
4424 return list()
4424 return list()
4425
4425
4426 @property
4426 @property
4427 def kwargs(self):
4427 def kwargs(self):
4428 try:
4428 try:
4429 return dict(self.task_kwargs or {})
4429 return dict(self.task_kwargs or {})
4430 except ValueError:
4430 except ValueError:
4431 return dict()
4431 return dict()
4432
4432
4433 def _as_raw(self, val):
4433 def _as_raw(self, val):
4434 if hasattr(val, 'de_coerce'):
4434 if hasattr(val, 'de_coerce'):
4435 val = val.de_coerce()
4435 val = val.de_coerce()
4436 if val:
4436 if val:
4437 val = json.dumps(val)
4437 val = json.dumps(val)
4438
4438
4439 return val
4439 return val
4440
4440
4441 @property
4441 @property
4442 def schedule_definition_raw(self):
4442 def schedule_definition_raw(self):
4443 return self._as_raw(self.schedule_definition)
4443 return self._as_raw(self.schedule_definition)
4444
4444
4445 @property
4445 @property
4446 def args_raw(self):
4446 def args_raw(self):
4447 return self._as_raw(self.task_args)
4447 return self._as_raw(self.task_args)
4448
4448
4449 @property
4449 @property
4450 def kwargs_raw(self):
4450 def kwargs_raw(self):
4451 return self._as_raw(self.task_kwargs)
4451 return self._as_raw(self.task_kwargs)
4452
4452
4453 def __repr__(self):
4453 def __repr__(self):
4454 return '<DB:ScheduleEntry({}:{})>'.format(
4454 return '<DB:ScheduleEntry({}:{})>'.format(
4455 self.schedule_entry_id, self.schedule_name)
4455 self.schedule_entry_id, self.schedule_name)
4456
4456
4457
4457
4458 @event.listens_for(ScheduleEntry, 'before_update')
4458 @event.listens_for(ScheduleEntry, 'before_update')
4459 def update_task_uid(mapper, connection, target):
4459 def update_task_uid(mapper, connection, target):
4460 target.task_uid = ScheduleEntry.get_uid(target)
4460 target.task_uid = ScheduleEntry.get_uid(target)
4461
4461
4462
4462
4463 @event.listens_for(ScheduleEntry, 'before_insert')
4463 @event.listens_for(ScheduleEntry, 'before_insert')
4464 def set_task_uid(mapper, connection, target):
4464 def set_task_uid(mapper, connection, target):
4465 target.task_uid = ScheduleEntry.get_uid(target)
4465 target.task_uid = ScheduleEntry.get_uid(target)
4466
4466
4467
4467
4468 class _BaseBranchPerms(BaseModel):
4468 class _BaseBranchPerms(BaseModel):
4469 @classmethod
4469 @classmethod
4470 def compute_hash(cls, value):
4470 def compute_hash(cls, value):
4471 return md5_safe(value)
4471 return md5_safe(value)
4472
4472
4473 @hybrid_property
4473 @hybrid_property
4474 def branch_pattern(self):
4474 def branch_pattern(self):
4475 return self._branch_pattern or '*'
4475 return self._branch_pattern or '*'
4476
4476
4477 @hybrid_property
4477 @hybrid_property
4478 def branch_hash(self):
4478 def branch_hash(self):
4479 return self._branch_hash
4479 return self._branch_hash
4480
4480
4481 def _validate_glob(self, value):
4481 def _validate_glob(self, value):
4482 re.compile('^' + glob2re(value) + '$')
4482 re.compile('^' + glob2re(value) + '$')
4483
4483
4484 @branch_pattern.setter
4484 @branch_pattern.setter
4485 def branch_pattern(self, value):
4485 def branch_pattern(self, value):
4486 self._validate_glob(value)
4486 self._validate_glob(value)
4487 self._branch_pattern = value or '*'
4487 self._branch_pattern = value or '*'
4488 # set the Hash when setting the branch pattern
4488 # set the Hash when setting the branch pattern
4489 self._branch_hash = self.compute_hash(self._branch_pattern)
4489 self._branch_hash = self.compute_hash(self._branch_pattern)
4490
4490
4491 def matches(self, branch):
4491 def matches(self, branch):
4492 """
4492 """
4493 Check if this the branch matches entry
4493 Check if this the branch matches entry
4494
4494
4495 :param branch: branch name for the commit
4495 :param branch: branch name for the commit
4496 """
4496 """
4497
4497
4498 branch = branch or ''
4498 branch = branch or ''
4499
4499
4500 branch_matches = True
4500 branch_matches = True
4501 if branch:
4501 if branch:
4502 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4502 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4503 branch_matches = bool(branch_regex.search(branch))
4503 branch_matches = bool(branch_regex.search(branch))
4504
4504
4505 return branch_matches
4505 return branch_matches
4506
4506
4507
4507
4508 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4508 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4509 __tablename__ = 'user_to_repo_branch_permissions'
4509 __tablename__ = 'user_to_repo_branch_permissions'
4510 __table_args__ = (
4510 __table_args__ = (
4511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4512 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4512 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4513 )
4513 )
4514
4514
4515 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4515 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4516
4516
4517 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4517 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4518 repo = relationship('Repository', backref='user_branch_perms')
4518 repo = relationship('Repository', backref='user_branch_perms')
4519
4519
4520 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4520 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4521 permission = relationship('Permission')
4521 permission = relationship('Permission')
4522
4522
4523 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4523 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4524 user_repo_to_perm = relationship('UserRepoToPerm')
4524 user_repo_to_perm = relationship('UserRepoToPerm')
4525
4525
4526 rule_order = Column('rule_order', Integer(), nullable=False)
4526 rule_order = Column('rule_order', Integer(), nullable=False)
4527 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4527 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4528 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4528 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4529
4529
4530 def __unicode__(self):
4530 def __unicode__(self):
4531 return u'<UserBranchPermission(%s => %r)>' % (
4531 return u'<UserBranchPermission(%s => %r)>' % (
4532 self.user_repo_to_perm, self.branch_pattern)
4532 self.user_repo_to_perm, self.branch_pattern)
4533
4533
4534
4534
4535 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4535 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4536 __tablename__ = 'user_group_to_repo_branch_permissions'
4536 __tablename__ = 'user_group_to_repo_branch_permissions'
4537 __table_args__ = (
4537 __table_args__ = (
4538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4540 )
4540 )
4541
4541
4542 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4542 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4543
4543
4544 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4544 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4545 repo = relationship('Repository', backref='user_group_branch_perms')
4545 repo = relationship('Repository', backref='user_group_branch_perms')
4546
4546
4547 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4547 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4548 permission = relationship('Permission')
4548 permission = relationship('Permission')
4549
4549
4550 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4550 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4551 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4551 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4552
4552
4553 rule_order = Column('rule_order', Integer(), nullable=False)
4553 rule_order = Column('rule_order', Integer(), nullable=False)
4554 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4554 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4555 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4555 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4556
4556
4557 def __unicode__(self):
4557 def __unicode__(self):
4558 return u'<UserBranchPermission(%s => %r)>' % (
4558 return u'<UserBranchPermission(%s => %r)>' % (
4559 self.user_group_repo_to_perm, self.branch_pattern)
4559 self.user_group_repo_to_perm, self.branch_pattern)
4560
4560
4561
4561
4562 class DbMigrateVersion(Base, BaseModel):
4562 class DbMigrateVersion(Base, BaseModel):
4563 __tablename__ = 'db_migrate_version'
4563 __tablename__ = 'db_migrate_version'
4564 __table_args__ = (
4564 __table_args__ = (
4565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4567 )
4567 )
4568 repository_id = Column('repository_id', String(250), primary_key=True)
4568 repository_id = Column('repository_id', String(250), primary_key=True)
4569 repository_path = Column('repository_path', Text)
4569 repository_path = Column('repository_path', Text)
4570 version = Column('version', Integer)
4570 version = Column('version', Integer)
4571
4571
4572
4572
4573 class DbSession(Base, BaseModel):
4573 class DbSession(Base, BaseModel):
4574 __tablename__ = 'db_session'
4574 __tablename__ = 'db_session'
4575 __table_args__ = (
4575 __table_args__ = (
4576 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4576 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4577 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4577 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4578 )
4578 )
4579
4579
4580 def __repr__(self):
4580 def __repr__(self):
4581 return '<DB:DbSession({})>'.format(self.id)
4581 return '<DB:DbSession({})>'.format(self.id)
4582
4582
4583 id = Column('id', Integer())
4583 id = Column('id', Integer())
4584 namespace = Column('namespace', String(255), primary_key=True)
4584 namespace = Column('namespace', String(255), primary_key=True)
4585 accessed = Column('accessed', DateTime, nullable=False)
4585 accessed = Column('accessed', DateTime, nullable=False)
4586 created = Column('created', DateTime, nullable=False)
4586 created = Column('created', DateTime, nullable=False)
4587 data = Column('data', PickleType, nullable=False)
4587 data = Column('data', PickleType, nullable=False)
@@ -1,4140 +1,4140 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.functions import coalesce, count # noqa
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from beaker.cache import cache_region
45 from beaker.cache import cache_region
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pyramid.threadlocal import get_current_request
48 from pyramid.threadlocal import get_current_request
49
49
50 from rhodecode.translation import _
50 from rhodecode.translation import _
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 from pyramid.httpexceptions import HTTPNotFound
211 from pyramid.httpexceptions import HTTPNotFound
212
212
213 try:
213 try:
214 id_ = int(id_)
214 id_ = int(id_)
215 except (TypeError, ValueError):
215 except (TypeError, ValueError):
216 raise HTTPNotFound()
216 raise HTTPNotFound()
217
217
218 res = cls.query().get(id_)
218 res = cls.query().get(id_)
219 if not res:
219 if not res:
220 raise HTTPNotFound()
220 raise HTTPNotFound()
221 return res
221 return res
222
222
223 @classmethod
223 @classmethod
224 def getAll(cls):
224 def getAll(cls):
225 # deprecated and left for backward compatibility
225 # deprecated and left for backward compatibility
226 return cls.get_all()
226 return cls.get_all()
227
227
228 @classmethod
228 @classmethod
229 def get_all(cls):
229 def get_all(cls):
230 return cls.query().all()
230 return cls.query().all()
231
231
232 @classmethod
232 @classmethod
233 def delete(cls, id_):
233 def delete(cls, id_):
234 obj = cls.query().get(id_)
234 obj = cls.query().get(id_)
235 Session().delete(obj)
235 Session().delete(obj)
236
236
237 @classmethod
237 @classmethod
238 def identity_cache(cls, session, attr_name, value):
238 def identity_cache(cls, session, attr_name, value):
239 exist_in_session = []
239 exist_in_session = []
240 for (item_cls, pkey), instance in session.identity_map.items():
240 for (item_cls, pkey), instance in session.identity_map.items():
241 if cls == item_cls and getattr(instance, attr_name) == value:
241 if cls == item_cls and getattr(instance, attr_name) == value:
242 exist_in_session.append(instance)
242 exist_in_session.append(instance)
243 if exist_in_session:
243 if exist_in_session:
244 if len(exist_in_session) == 1:
244 if len(exist_in_session) == 1:
245 return exist_in_session[0]
245 return exist_in_session[0]
246 log.exception(
246 log.exception(
247 'multiple objects with attr %s and '
247 'multiple objects with attr %s and '
248 'value %s found with same name: %r',
248 'value %s found with same name: %r',
249 attr_name, value, exist_in_session)
249 attr_name, value, exist_in_session)
250
250
251 def __repr__(self):
251 def __repr__(self):
252 if hasattr(self, '__unicode__'):
252 if hasattr(self, '__unicode__'):
253 # python repr needs to return str
253 # python repr needs to return str
254 try:
254 try:
255 return safe_str(self.__unicode__())
255 return safe_str(self.__unicode__())
256 except UnicodeDecodeError:
256 except UnicodeDecodeError:
257 pass
257 pass
258 return '<DB:%s>' % (self.__class__.__name__)
258 return '<DB:%s>' % (self.__class__.__name__)
259
259
260
260
261 class RhodeCodeSetting(Base, BaseModel):
261 class RhodeCodeSetting(Base, BaseModel):
262 __tablename__ = 'rhodecode_settings'
262 __tablename__ = 'rhodecode_settings'
263 __table_args__ = (
263 __table_args__ = (
264 UniqueConstraint('app_settings_name'),
264 UniqueConstraint('app_settings_name'),
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
267 )
267 )
268
268
269 SETTINGS_TYPES = {
269 SETTINGS_TYPES = {
270 'str': safe_str,
270 'str': safe_str,
271 'int': safe_int,
271 'int': safe_int,
272 'unicode': safe_unicode,
272 'unicode': safe_unicode,
273 'bool': str2bool,
273 'bool': str2bool,
274 'list': functools.partial(aslist, sep=',')
274 'list': functools.partial(aslist, sep=',')
275 }
275 }
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
277 GLOBAL_CONF_KEY = 'app_settings'
277 GLOBAL_CONF_KEY = 'app_settings'
278
278
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
283
283
284 def __init__(self, key='', val='', type='unicode'):
284 def __init__(self, key='', val='', type='unicode'):
285 self.app_settings_name = key
285 self.app_settings_name = key
286 self.app_settings_type = type
286 self.app_settings_type = type
287 self.app_settings_value = val
287 self.app_settings_value = val
288
288
289 @validates('_app_settings_value')
289 @validates('_app_settings_value')
290 def validate_settings_value(self, key, val):
290 def validate_settings_value(self, key, val):
291 assert type(val) == unicode
291 assert type(val) == unicode
292 return val
292 return val
293
293
294 @hybrid_property
294 @hybrid_property
295 def app_settings_value(self):
295 def app_settings_value(self):
296 v = self._app_settings_value
296 v = self._app_settings_value
297 _type = self.app_settings_type
297 _type = self.app_settings_type
298 if _type:
298 if _type:
299 _type = self.app_settings_type.split('.')[0]
299 _type = self.app_settings_type.split('.')[0]
300 # decode the encrypted value
300 # decode the encrypted value
301 if 'encrypted' in self.app_settings_type:
301 if 'encrypted' in self.app_settings_type:
302 cipher = EncryptedTextValue()
302 cipher = EncryptedTextValue()
303 v = safe_unicode(cipher.process_result_value(v, None))
303 v = safe_unicode(cipher.process_result_value(v, None))
304
304
305 converter = self.SETTINGS_TYPES.get(_type) or \
305 converter = self.SETTINGS_TYPES.get(_type) or \
306 self.SETTINGS_TYPES['unicode']
306 self.SETTINGS_TYPES['unicode']
307 return converter(v)
307 return converter(v)
308
308
309 @app_settings_value.setter
309 @app_settings_value.setter
310 def app_settings_value(self, val):
310 def app_settings_value(self, val):
311 """
311 """
312 Setter that will always make sure we use unicode in app_settings_value
312 Setter that will always make sure we use unicode in app_settings_value
313
313
314 :param val:
314 :param val:
315 """
315 """
316 val = safe_unicode(val)
316 val = safe_unicode(val)
317 # encode the encrypted value
317 # encode the encrypted value
318 if 'encrypted' in self.app_settings_type:
318 if 'encrypted' in self.app_settings_type:
319 cipher = EncryptedTextValue()
319 cipher = EncryptedTextValue()
320 val = safe_unicode(cipher.process_bind_param(val, None))
320 val = safe_unicode(cipher.process_bind_param(val, None))
321 self._app_settings_value = val
321 self._app_settings_value = val
322
322
323 @hybrid_property
323 @hybrid_property
324 def app_settings_type(self):
324 def app_settings_type(self):
325 return self._app_settings_type
325 return self._app_settings_type
326
326
327 @app_settings_type.setter
327 @app_settings_type.setter
328 def app_settings_type(self, val):
328 def app_settings_type(self, val):
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
330 raise Exception('type must be one of %s got %s'
330 raise Exception('type must be one of %s got %s'
331 % (self.SETTINGS_TYPES.keys(), val))
331 % (self.SETTINGS_TYPES.keys(), val))
332 self._app_settings_type = val
332 self._app_settings_type = val
333
333
334 def __unicode__(self):
334 def __unicode__(self):
335 return u"<%s('%s:%s[%s]')>" % (
335 return u"<%s('%s:%s[%s]')>" % (
336 self.__class__.__name__,
336 self.__class__.__name__,
337 self.app_settings_name, self.app_settings_value,
337 self.app_settings_name, self.app_settings_value,
338 self.app_settings_type
338 self.app_settings_type
339 )
339 )
340
340
341
341
342 class RhodeCodeUi(Base, BaseModel):
342 class RhodeCodeUi(Base, BaseModel):
343 __tablename__ = 'rhodecode_ui'
343 __tablename__ = 'rhodecode_ui'
344 __table_args__ = (
344 __table_args__ = (
345 UniqueConstraint('ui_key'),
345 UniqueConstraint('ui_key'),
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
348 )
348 )
349
349
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
351 # HG
351 # HG
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
353 HOOK_PULL = 'outgoing.pull_logger'
353 HOOK_PULL = 'outgoing.pull_logger'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
355 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
355 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
356 HOOK_PUSH = 'changegroup.push_logger'
356 HOOK_PUSH = 'changegroup.push_logger'
357 HOOK_PUSH_KEY = 'pushkey.key_push'
357 HOOK_PUSH_KEY = 'pushkey.key_push'
358
358
359 # TODO: johbo: Unify way how hooks are configured for git and hg,
359 # TODO: johbo: Unify way how hooks are configured for git and hg,
360 # git part is currently hardcoded.
360 # git part is currently hardcoded.
361
361
362 # SVN PATTERNS
362 # SVN PATTERNS
363 SVN_BRANCH_ID = 'vcs_svn_branch'
363 SVN_BRANCH_ID = 'vcs_svn_branch'
364 SVN_TAG_ID = 'vcs_svn_tag'
364 SVN_TAG_ID = 'vcs_svn_tag'
365
365
366 ui_id = Column(
366 ui_id = Column(
367 "ui_id", Integer(), nullable=False, unique=True, default=None,
367 "ui_id", Integer(), nullable=False, unique=True, default=None,
368 primary_key=True)
368 primary_key=True)
369 ui_section = Column(
369 ui_section = Column(
370 "ui_section", String(255), nullable=True, unique=None, default=None)
370 "ui_section", String(255), nullable=True, unique=None, default=None)
371 ui_key = Column(
371 ui_key = Column(
372 "ui_key", String(255), nullable=True, unique=None, default=None)
372 "ui_key", String(255), nullable=True, unique=None, default=None)
373 ui_value = Column(
373 ui_value = Column(
374 "ui_value", String(255), nullable=True, unique=None, default=None)
374 "ui_value", String(255), nullable=True, unique=None, default=None)
375 ui_active = Column(
375 ui_active = Column(
376 "ui_active", Boolean(), nullable=True, unique=None, default=True)
376 "ui_active", Boolean(), nullable=True, unique=None, default=True)
377
377
378 def __repr__(self):
378 def __repr__(self):
379 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
379 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
380 self.ui_key, self.ui_value)
380 self.ui_key, self.ui_value)
381
381
382
382
383 class RepoRhodeCodeSetting(Base, BaseModel):
383 class RepoRhodeCodeSetting(Base, BaseModel):
384 __tablename__ = 'repo_rhodecode_settings'
384 __tablename__ = 'repo_rhodecode_settings'
385 __table_args__ = (
385 __table_args__ = (
386 UniqueConstraint(
386 UniqueConstraint(
387 'app_settings_name', 'repository_id',
387 'app_settings_name', 'repository_id',
388 name='uq_repo_rhodecode_setting_name_repo_id'),
388 name='uq_repo_rhodecode_setting_name_repo_id'),
389 {'extend_existing': True, 'mysql_engine': 'InnoDB',
389 {'extend_existing': True, 'mysql_engine': 'InnoDB',
390 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
390 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
391 )
391 )
392
392
393 repository_id = Column(
393 repository_id = Column(
394 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
394 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
395 nullable=False)
395 nullable=False)
396 app_settings_id = Column(
396 app_settings_id = Column(
397 "app_settings_id", Integer(), nullable=False, unique=True,
397 "app_settings_id", Integer(), nullable=False, unique=True,
398 default=None, primary_key=True)
398 default=None, primary_key=True)
399 app_settings_name = Column(
399 app_settings_name = Column(
400 "app_settings_name", String(255), nullable=True, unique=None,
400 "app_settings_name", String(255), nullable=True, unique=None,
401 default=None)
401 default=None)
402 _app_settings_value = Column(
402 _app_settings_value = Column(
403 "app_settings_value", String(4096), nullable=True, unique=None,
403 "app_settings_value", String(4096), nullable=True, unique=None,
404 default=None)
404 default=None)
405 _app_settings_type = Column(
405 _app_settings_type = Column(
406 "app_settings_type", String(255), nullable=True, unique=None,
406 "app_settings_type", String(255), nullable=True, unique=None,
407 default=None)
407 default=None)
408
408
409 repository = relationship('Repository')
409 repository = relationship('Repository')
410
410
411 def __init__(self, repository_id, key='', val='', type='unicode'):
411 def __init__(self, repository_id, key='', val='', type='unicode'):
412 self.repository_id = repository_id
412 self.repository_id = repository_id
413 self.app_settings_name = key
413 self.app_settings_name = key
414 self.app_settings_type = type
414 self.app_settings_type = type
415 self.app_settings_value = val
415 self.app_settings_value = val
416
416
417 @validates('_app_settings_value')
417 @validates('_app_settings_value')
418 def validate_settings_value(self, key, val):
418 def validate_settings_value(self, key, val):
419 assert type(val) == unicode
419 assert type(val) == unicode
420 return val
420 return val
421
421
422 @hybrid_property
422 @hybrid_property
423 def app_settings_value(self):
423 def app_settings_value(self):
424 v = self._app_settings_value
424 v = self._app_settings_value
425 type_ = self.app_settings_type
425 type_ = self.app_settings_type
426 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
426 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
427 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
427 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
428 return converter(v)
428 return converter(v)
429
429
430 @app_settings_value.setter
430 @app_settings_value.setter
431 def app_settings_value(self, val):
431 def app_settings_value(self, val):
432 """
432 """
433 Setter that will always make sure we use unicode in app_settings_value
433 Setter that will always make sure we use unicode in app_settings_value
434
434
435 :param val:
435 :param val:
436 """
436 """
437 self._app_settings_value = safe_unicode(val)
437 self._app_settings_value = safe_unicode(val)
438
438
439 @hybrid_property
439 @hybrid_property
440 def app_settings_type(self):
440 def app_settings_type(self):
441 return self._app_settings_type
441 return self._app_settings_type
442
442
443 @app_settings_type.setter
443 @app_settings_type.setter
444 def app_settings_type(self, val):
444 def app_settings_type(self, val):
445 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
445 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
446 if val not in SETTINGS_TYPES:
446 if val not in SETTINGS_TYPES:
447 raise Exception('type must be one of %s got %s'
447 raise Exception('type must be one of %s got %s'
448 % (SETTINGS_TYPES.keys(), val))
448 % (SETTINGS_TYPES.keys(), val))
449 self._app_settings_type = val
449 self._app_settings_type = val
450
450
451 def __unicode__(self):
451 def __unicode__(self):
452 return u"<%s('%s:%s:%s[%s]')>" % (
452 return u"<%s('%s:%s:%s[%s]')>" % (
453 self.__class__.__name__, self.repository.repo_name,
453 self.__class__.__name__, self.repository.repo_name,
454 self.app_settings_name, self.app_settings_value,
454 self.app_settings_name, self.app_settings_value,
455 self.app_settings_type
455 self.app_settings_type
456 )
456 )
457
457
458
458
459 class RepoRhodeCodeUi(Base, BaseModel):
459 class RepoRhodeCodeUi(Base, BaseModel):
460 __tablename__ = 'repo_rhodecode_ui'
460 __tablename__ = 'repo_rhodecode_ui'
461 __table_args__ = (
461 __table_args__ = (
462 UniqueConstraint(
462 UniqueConstraint(
463 'repository_id', 'ui_section', 'ui_key',
463 'repository_id', 'ui_section', 'ui_key',
464 name='uq_repo_rhodecode_ui_repository_id_section_key'),
464 name='uq_repo_rhodecode_ui_repository_id_section_key'),
465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
466 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
466 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
467 )
467 )
468
468
469 repository_id = Column(
469 repository_id = Column(
470 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
470 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
471 nullable=False)
471 nullable=False)
472 ui_id = Column(
472 ui_id = Column(
473 "ui_id", Integer(), nullable=False, unique=True, default=None,
473 "ui_id", Integer(), nullable=False, unique=True, default=None,
474 primary_key=True)
474 primary_key=True)
475 ui_section = Column(
475 ui_section = Column(
476 "ui_section", String(255), nullable=True, unique=None, default=None)
476 "ui_section", String(255), nullable=True, unique=None, default=None)
477 ui_key = Column(
477 ui_key = Column(
478 "ui_key", String(255), nullable=True, unique=None, default=None)
478 "ui_key", String(255), nullable=True, unique=None, default=None)
479 ui_value = Column(
479 ui_value = Column(
480 "ui_value", String(255), nullable=True, unique=None, default=None)
480 "ui_value", String(255), nullable=True, unique=None, default=None)
481 ui_active = Column(
481 ui_active = Column(
482 "ui_active", Boolean(), nullable=True, unique=None, default=True)
482 "ui_active", Boolean(), nullable=True, unique=None, default=True)
483
483
484 repository = relationship('Repository')
484 repository = relationship('Repository')
485
485
486 def __repr__(self):
486 def __repr__(self):
487 return '<%s[%s:%s]%s=>%s]>' % (
487 return '<%s[%s:%s]%s=>%s]>' % (
488 self.__class__.__name__, self.repository.repo_name,
488 self.__class__.__name__, self.repository.repo_name,
489 self.ui_section, self.ui_key, self.ui_value)
489 self.ui_section, self.ui_key, self.ui_value)
490
490
491
491
492 class User(Base, BaseModel):
492 class User(Base, BaseModel):
493 __tablename__ = 'users'
493 __tablename__ = 'users'
494 __table_args__ = (
494 __table_args__ = (
495 UniqueConstraint('username'), UniqueConstraint('email'),
495 UniqueConstraint('username'), UniqueConstraint('email'),
496 Index('u_username_idx', 'username'),
496 Index('u_username_idx', 'username'),
497 Index('u_email_idx', 'email'),
497 Index('u_email_idx', 'email'),
498 {'extend_existing': True, 'mysql_engine': 'InnoDB',
498 {'extend_existing': True, 'mysql_engine': 'InnoDB',
499 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
499 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
500 )
500 )
501 DEFAULT_USER = 'default'
501 DEFAULT_USER = 'default'
502 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
502 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
503 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
503 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
504
504
505 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
505 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
506 username = Column("username", String(255), nullable=True, unique=None, default=None)
506 username = Column("username", String(255), nullable=True, unique=None, default=None)
507 password = Column("password", String(255), nullable=True, unique=None, default=None)
507 password = Column("password", String(255), nullable=True, unique=None, default=None)
508 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
508 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
509 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
509 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
510 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
510 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
511 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
511 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
512 _email = Column("email", String(255), nullable=True, unique=None, default=None)
512 _email = Column("email", String(255), nullable=True, unique=None, default=None)
513 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
513 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
514 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
514 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
515
515
516 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
516 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
517 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
517 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
518 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
518 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
519 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
519 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
520 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
520 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
521 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
521 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
522
522
523 user_log = relationship('UserLog')
523 user_log = relationship('UserLog')
524 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
524 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
525
525
526 repositories = relationship('Repository')
526 repositories = relationship('Repository')
527 repository_groups = relationship('RepoGroup')
527 repository_groups = relationship('RepoGroup')
528 user_groups = relationship('UserGroup')
528 user_groups = relationship('UserGroup')
529
529
530 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
530 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
531 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
531 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
532
532
533 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
533 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
534 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
534 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
535 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
535 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
536
536
537 group_member = relationship('UserGroupMember', cascade='all')
537 group_member = relationship('UserGroupMember', cascade='all')
538
538
539 notifications = relationship('UserNotification', cascade='all')
539 notifications = relationship('UserNotification', cascade='all')
540 # notifications assigned to this user
540 # notifications assigned to this user
541 user_created_notifications = relationship('Notification', cascade='all')
541 user_created_notifications = relationship('Notification', cascade='all')
542 # comments created by this user
542 # comments created by this user
543 user_comments = relationship('ChangesetComment', cascade='all')
543 user_comments = relationship('ChangesetComment', cascade='all')
544 # user profile extra info
544 # user profile extra info
545 user_emails = relationship('UserEmailMap', cascade='all')
545 user_emails = relationship('UserEmailMap', cascade='all')
546 user_ip_map = relationship('UserIpMap', cascade='all')
546 user_ip_map = relationship('UserIpMap', cascade='all')
547 user_auth_tokens = relationship('UserApiKeys', cascade='all')
547 user_auth_tokens = relationship('UserApiKeys', cascade='all')
548 user_ssh_keys = relationship('UserSshKeys', cascade='all')
548 user_ssh_keys = relationship('UserSshKeys', cascade='all')
549
549
550 # gists
550 # gists
551 user_gists = relationship('Gist', cascade='all')
551 user_gists = relationship('Gist', cascade='all')
552 # user pull requests
552 # user pull requests
553 user_pull_requests = relationship('PullRequest', cascade='all')
553 user_pull_requests = relationship('PullRequest', cascade='all')
554 # external identities
554 # external identities
555 extenal_identities = relationship(
555 extenal_identities = relationship(
556 'ExternalIdentity',
556 'ExternalIdentity',
557 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
557 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
558 cascade='all')
558 cascade='all')
559
559
560 def __unicode__(self):
560 def __unicode__(self):
561 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
561 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
562 self.user_id, self.username)
562 self.user_id, self.username)
563
563
564 @hybrid_property
564 @hybrid_property
565 def email(self):
565 def email(self):
566 return self._email
566 return self._email
567
567
568 @email.setter
568 @email.setter
569 def email(self, val):
569 def email(self, val):
570 self._email = val.lower() if val else None
570 self._email = val.lower() if val else None
571
571
572 @hybrid_property
572 @hybrid_property
573 def first_name(self):
573 def first_name(self):
574 from rhodecode.lib import helpers as h
574 from rhodecode.lib import helpers as h
575 if self.name:
575 if self.name:
576 return h.escape(self.name)
576 return h.escape(self.name)
577 return self.name
577 return self.name
578
578
579 @hybrid_property
579 @hybrid_property
580 def last_name(self):
580 def last_name(self):
581 from rhodecode.lib import helpers as h
581 from rhodecode.lib import helpers as h
582 if self.lastname:
582 if self.lastname:
583 return h.escape(self.lastname)
583 return h.escape(self.lastname)
584 return self.lastname
584 return self.lastname
585
585
586 @hybrid_property
586 @hybrid_property
587 def api_key(self):
587 def api_key(self):
588 """
588 """
589 Fetch if exist an auth-token with role ALL connected to this user
589 Fetch if exist an auth-token with role ALL connected to this user
590 """
590 """
591 user_auth_token = UserApiKeys.query()\
591 user_auth_token = UserApiKeys.query()\
592 .filter(UserApiKeys.user_id == self.user_id)\
592 .filter(UserApiKeys.user_id == self.user_id)\
593 .filter(or_(UserApiKeys.expires == -1,
593 .filter(or_(UserApiKeys.expires == -1,
594 UserApiKeys.expires >= time.time()))\
594 UserApiKeys.expires >= time.time()))\
595 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
595 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
596 if user_auth_token:
596 if user_auth_token:
597 user_auth_token = user_auth_token.api_key
597 user_auth_token = user_auth_token.api_key
598
598
599 return user_auth_token
599 return user_auth_token
600
600
601 @api_key.setter
601 @api_key.setter
602 def api_key(self, val):
602 def api_key(self, val):
603 # don't allow to set API key this is deprecated for now
603 # don't allow to set API key this is deprecated for now
604 self._api_key = None
604 self._api_key = None
605
605
606 @property
606 @property
607 def reviewer_pull_requests(self):
607 def reviewer_pull_requests(self):
608 return PullRequestReviewers.query() \
608 return PullRequestReviewers.query() \
609 .options(joinedload(PullRequestReviewers.pull_request)) \
609 .options(joinedload(PullRequestReviewers.pull_request)) \
610 .filter(PullRequestReviewers.user_id == self.user_id) \
610 .filter(PullRequestReviewers.user_id == self.user_id) \
611 .all()
611 .all()
612
612
613 @property
613 @property
614 def firstname(self):
614 def firstname(self):
615 # alias for future
615 # alias for future
616 return self.name
616 return self.name
617
617
618 @property
618 @property
619 def emails(self):
619 def emails(self):
620 other = UserEmailMap.query()\
620 other = UserEmailMap.query()\
621 .filter(UserEmailMap.user == self) \
621 .filter(UserEmailMap.user == self) \
622 .order_by(UserEmailMap.email_id.asc()) \
622 .order_by(UserEmailMap.email_id.asc()) \
623 .all()
623 .all()
624 return [self.email] + [x.email for x in other]
624 return [self.email] + [x.email for x in other]
625
625
626 @property
626 @property
627 def auth_tokens(self):
627 def auth_tokens(self):
628 auth_tokens = self.get_auth_tokens()
628 auth_tokens = self.get_auth_tokens()
629 return [x.api_key for x in auth_tokens]
629 return [x.api_key for x in auth_tokens]
630
630
631 def get_auth_tokens(self):
631 def get_auth_tokens(self):
632 return UserApiKeys.query()\
632 return UserApiKeys.query()\
633 .filter(UserApiKeys.user == self)\
633 .filter(UserApiKeys.user == self)\
634 .order_by(UserApiKeys.user_api_key_id.asc())\
634 .order_by(UserApiKeys.user_api_key_id.asc())\
635 .all()
635 .all()
636
636
637 @property
637 @property
638 def feed_token(self):
638 def feed_token(self):
639 return self.get_feed_token()
639 return self.get_feed_token()
640
640
641 def get_feed_token(self):
641 def get_feed_token(self):
642 feed_tokens = UserApiKeys.query()\
642 feed_tokens = UserApiKeys.query()\
643 .filter(UserApiKeys.user == self)\
643 .filter(UserApiKeys.user == self)\
644 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
644 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
645 .all()
645 .all()
646 if feed_tokens:
646 if feed_tokens:
647 return feed_tokens[0].api_key
647 return feed_tokens[0].api_key
648 return 'NO_FEED_TOKEN_AVAILABLE'
648 return 'NO_FEED_TOKEN_AVAILABLE'
649
649
650 @classmethod
650 @classmethod
651 def extra_valid_auth_tokens(cls, user, role=None):
651 def extra_valid_auth_tokens(cls, user, role=None):
652 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
652 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
653 .filter(or_(UserApiKeys.expires == -1,
653 .filter(or_(UserApiKeys.expires == -1,
654 UserApiKeys.expires >= time.time()))
654 UserApiKeys.expires >= time.time()))
655 if role:
655 if role:
656 tokens = tokens.filter(or_(UserApiKeys.role == role,
656 tokens = tokens.filter(or_(UserApiKeys.role == role,
657 UserApiKeys.role == UserApiKeys.ROLE_ALL))
657 UserApiKeys.role == UserApiKeys.ROLE_ALL))
658 return tokens.all()
658 return tokens.all()
659
659
660 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
660 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
661 from rhodecode.lib import auth
661 from rhodecode.lib import auth
662
662
663 log.debug('Trying to authenticate user: %s via auth-token, '
663 log.debug('Trying to authenticate user: %s via auth-token, '
664 'and roles: %s', self, roles)
664 'and roles: %s', self, roles)
665
665
666 if not auth_token:
666 if not auth_token:
667 return False
667 return False
668
668
669 crypto_backend = auth.crypto_backend()
669 crypto_backend = auth.crypto_backend()
670
670
671 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
671 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
672 tokens_q = UserApiKeys.query()\
672 tokens_q = UserApiKeys.query()\
673 .filter(UserApiKeys.user_id == self.user_id)\
673 .filter(UserApiKeys.user_id == self.user_id)\
674 .filter(or_(UserApiKeys.expires == -1,
674 .filter(or_(UserApiKeys.expires == -1,
675 UserApiKeys.expires >= time.time()))
675 UserApiKeys.expires >= time.time()))
676
676
677 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
677 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
678
678
679 plain_tokens = []
679 plain_tokens = []
680 hash_tokens = []
680 hash_tokens = []
681
681
682 for token in tokens_q.all():
682 for token in tokens_q.all():
683 # verify scope first
683 # verify scope first
684 if token.repo_id:
684 if token.repo_id:
685 # token has a scope, we need to verify it
685 # token has a scope, we need to verify it
686 if scope_repo_id != token.repo_id:
686 if scope_repo_id != token.repo_id:
687 log.debug(
687 log.debug(
688 'Scope mismatch: token has a set repo scope: %s, '
688 'Scope mismatch: token has a set repo scope: %s, '
689 'and calling scope is:%s, skipping further checks',
689 'and calling scope is:%s, skipping further checks',
690 token.repo, scope_repo_id)
690 token.repo, scope_repo_id)
691 # token has a scope, and it doesn't match, skip token
691 # token has a scope, and it doesn't match, skip token
692 continue
692 continue
693
693
694 if token.api_key.startswith(crypto_backend.ENC_PREF):
694 if token.api_key.startswith(crypto_backend.ENC_PREF):
695 hash_tokens.append(token.api_key)
695 hash_tokens.append(token.api_key)
696 else:
696 else:
697 plain_tokens.append(token.api_key)
697 plain_tokens.append(token.api_key)
698
698
699 is_plain_match = auth_token in plain_tokens
699 is_plain_match = auth_token in plain_tokens
700 if is_plain_match:
700 if is_plain_match:
701 return True
701 return True
702
702
703 for hashed in hash_tokens:
703 for hashed in hash_tokens:
704 # TODO(marcink): this is expensive to calculate, but most secure
704 # TODO(marcink): this is expensive to calculate, but most secure
705 match = crypto_backend.hash_check(auth_token, hashed)
705 match = crypto_backend.hash_check(auth_token, hashed)
706 if match:
706 if match:
707 return True
707 return True
708
708
709 return False
709 return False
710
710
711 @property
711 @property
712 def ip_addresses(self):
712 def ip_addresses(self):
713 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
713 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
714 return [x.ip_addr for x in ret]
714 return [x.ip_addr for x in ret]
715
715
716 @property
716 @property
717 def username_and_name(self):
717 def username_and_name(self):
718 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
718 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
719
719
720 @property
720 @property
721 def username_or_name_or_email(self):
721 def username_or_name_or_email(self):
722 full_name = self.full_name if self.full_name is not ' ' else None
722 full_name = self.full_name if self.full_name is not ' ' else None
723 return self.username or full_name or self.email
723 return self.username or full_name or self.email
724
724
725 @property
725 @property
726 def full_name(self):
726 def full_name(self):
727 return '%s %s' % (self.first_name, self.last_name)
727 return '%s %s' % (self.first_name, self.last_name)
728
728
729 @property
729 @property
730 def full_name_or_username(self):
730 def full_name_or_username(self):
731 return ('%s %s' % (self.first_name, self.last_name)
731 return ('%s %s' % (self.first_name, self.last_name)
732 if (self.first_name and self.last_name) else self.username)
732 if (self.first_name and self.last_name) else self.username)
733
733
734 @property
734 @property
735 def full_contact(self):
735 def full_contact(self):
736 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
736 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
737
737
738 @property
738 @property
739 def short_contact(self):
739 def short_contact(self):
740 return '%s %s' % (self.first_name, self.last_name)
740 return '%s %s' % (self.first_name, self.last_name)
741
741
742 @property
742 @property
743 def is_admin(self):
743 def is_admin(self):
744 return self.admin
744 return self.admin
745
745
746 @property
746 @property
747 def AuthUser(self):
747 def AuthUser(self):
748 """
748 """
749 Returns instance of AuthUser for this user
749 Returns instance of AuthUser for this user
750 """
750 """
751 from rhodecode.lib.auth import AuthUser
751 from rhodecode.lib.auth import AuthUser
752 return AuthUser(user_id=self.user_id, username=self.username)
752 return AuthUser(user_id=self.user_id, username=self.username)
753
753
754 @hybrid_property
754 @hybrid_property
755 def user_data(self):
755 def user_data(self):
756 if not self._user_data:
756 if not self._user_data:
757 return {}
757 return {}
758
758
759 try:
759 try:
760 return json.loads(self._user_data)
760 return json.loads(self._user_data)
761 except TypeError:
761 except TypeError:
762 return {}
762 return {}
763
763
764 @user_data.setter
764 @user_data.setter
765 def user_data(self, val):
765 def user_data(self, val):
766 if not isinstance(val, dict):
766 if not isinstance(val, dict):
767 raise Exception('user_data must be dict, got %s' % type(val))
767 raise Exception('user_data must be dict, got %s' % type(val))
768 try:
768 try:
769 self._user_data = json.dumps(val)
769 self._user_data = json.dumps(val)
770 except Exception:
770 except Exception:
771 log.error(traceback.format_exc())
771 log.error(traceback.format_exc())
772
772
773 @classmethod
773 @classmethod
774 def get_by_username(cls, username, case_insensitive=False,
774 def get_by_username(cls, username, case_insensitive=False,
775 cache=False, identity_cache=False):
775 cache=False, identity_cache=False):
776 session = Session()
776 session = Session()
777
777
778 if case_insensitive:
778 if case_insensitive:
779 q = cls.query().filter(
779 q = cls.query().filter(
780 func.lower(cls.username) == func.lower(username))
780 func.lower(cls.username) == func.lower(username))
781 else:
781 else:
782 q = cls.query().filter(cls.username == username)
782 q = cls.query().filter(cls.username == username)
783
783
784 if cache:
784 if cache:
785 if identity_cache:
785 if identity_cache:
786 val = cls.identity_cache(session, 'username', username)
786 val = cls.identity_cache(session, 'username', username)
787 if val:
787 if val:
788 return val
788 return val
789 else:
789 else:
790 cache_key = "get_user_by_name_%s" % _hash_key(username)
790 cache_key = "get_user_by_name_%s" % _hash_key(username)
791 q = q.options(
791 q = q.options(
792 FromCache("sql_cache_short", cache_key))
792 FromCache("sql_cache_short", cache_key))
793
793
794 return q.scalar()
794 return q.scalar()
795
795
796 @classmethod
796 @classmethod
797 def get_by_auth_token(cls, auth_token, cache=False):
797 def get_by_auth_token(cls, auth_token, cache=False):
798 q = UserApiKeys.query()\
798 q = UserApiKeys.query()\
799 .filter(UserApiKeys.api_key == auth_token)\
799 .filter(UserApiKeys.api_key == auth_token)\
800 .filter(or_(UserApiKeys.expires == -1,
800 .filter(or_(UserApiKeys.expires == -1,
801 UserApiKeys.expires >= time.time()))
801 UserApiKeys.expires >= time.time()))
802 if cache:
802 if cache:
803 q = q.options(
803 q = q.options(
804 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
804 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
805
805
806 match = q.first()
806 match = q.first()
807 if match:
807 if match:
808 return match.user
808 return match.user
809
809
810 @classmethod
810 @classmethod
811 def get_by_email(cls, email, case_insensitive=False, cache=False):
811 def get_by_email(cls, email, case_insensitive=False, cache=False):
812
812
813 if case_insensitive:
813 if case_insensitive:
814 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
814 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
815
815
816 else:
816 else:
817 q = cls.query().filter(cls.email == email)
817 q = cls.query().filter(cls.email == email)
818
818
819 email_key = _hash_key(email)
819 email_key = _hash_key(email)
820 if cache:
820 if cache:
821 q = q.options(
821 q = q.options(
822 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
822 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
823
823
824 ret = q.scalar()
824 ret = q.scalar()
825 if ret is None:
825 if ret is None:
826 q = UserEmailMap.query()
826 q = UserEmailMap.query()
827 # try fetching in alternate email map
827 # try fetching in alternate email map
828 if case_insensitive:
828 if case_insensitive:
829 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
829 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
830 else:
830 else:
831 q = q.filter(UserEmailMap.email == email)
831 q = q.filter(UserEmailMap.email == email)
832 q = q.options(joinedload(UserEmailMap.user))
832 q = q.options(joinedload(UserEmailMap.user))
833 if cache:
833 if cache:
834 q = q.options(
834 q = q.options(
835 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
835 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
836 ret = getattr(q.scalar(), 'user', None)
836 ret = getattr(q.scalar(), 'user', None)
837
837
838 return ret
838 return ret
839
839
840 @classmethod
840 @classmethod
841 def get_from_cs_author(cls, author):
841 def get_from_cs_author(cls, author):
842 """
842 """
843 Tries to get User objects out of commit author string
843 Tries to get User objects out of commit author string
844
844
845 :param author:
845 :param author:
846 """
846 """
847 from rhodecode.lib.helpers import email, author_name
847 from rhodecode.lib.helpers import email, author_name
848 # Valid email in the attribute passed, see if they're in the system
848 # Valid email in the attribute passed, see if they're in the system
849 _email = email(author)
849 _email = email(author)
850 if _email:
850 if _email:
851 user = cls.get_by_email(_email, case_insensitive=True)
851 user = cls.get_by_email(_email, case_insensitive=True)
852 if user:
852 if user:
853 return user
853 return user
854 # Maybe we can match by username?
854 # Maybe we can match by username?
855 _author = author_name(author)
855 _author = author_name(author)
856 user = cls.get_by_username(_author, case_insensitive=True)
856 user = cls.get_by_username(_author, case_insensitive=True)
857 if user:
857 if user:
858 return user
858 return user
859
859
860 def update_userdata(self, **kwargs):
860 def update_userdata(self, **kwargs):
861 usr = self
861 usr = self
862 old = usr.user_data
862 old = usr.user_data
863 old.update(**kwargs)
863 old.update(**kwargs)
864 usr.user_data = old
864 usr.user_data = old
865 Session().add(usr)
865 Session().add(usr)
866 log.debug('updated userdata with ', kwargs)
866 log.debug('updated userdata with ', kwargs)
867
867
868 def update_lastlogin(self):
868 def update_lastlogin(self):
869 """Update user lastlogin"""
869 """Update user lastlogin"""
870 self.last_login = datetime.datetime.now()
870 self.last_login = datetime.datetime.now()
871 Session().add(self)
871 Session().add(self)
872 log.debug('updated user %s lastlogin', self.username)
872 log.debug('updated user %s lastlogin', self.username)
873
873
874 def update_lastactivity(self):
874 def update_lastactivity(self):
875 """Update user lastactivity"""
875 """Update user lastactivity"""
876 self.last_activity = datetime.datetime.now()
876 self.last_activity = datetime.datetime.now()
877 Session().add(self)
877 Session().add(self)
878 log.debug('updated user %s lastactivity', self.username)
878 log.debug('updated user %s lastactivity', self.username)
879
879
880 def update_password(self, new_password):
880 def update_password(self, new_password):
881 from rhodecode.lib.auth import get_crypt_password
881 from rhodecode.lib.auth import get_crypt_password
882
882
883 self.password = get_crypt_password(new_password)
883 self.password = get_crypt_password(new_password)
884 Session().add(self)
884 Session().add(self)
885
885
886 @classmethod
886 @classmethod
887 def get_first_super_admin(cls):
887 def get_first_super_admin(cls):
888 user = User.query().filter(User.admin == true()).first()
888 user = User.query().filter(User.admin == true()).first()
889 if user is None:
889 if user is None:
890 raise Exception('FATAL: Missing administrative account!')
890 raise Exception('FATAL: Missing administrative account!')
891 return user
891 return user
892
892
893 @classmethod
893 @classmethod
894 def get_all_super_admins(cls):
894 def get_all_super_admins(cls):
895 """
895 """
896 Returns all admin accounts sorted by username
896 Returns all admin accounts sorted by username
897 """
897 """
898 return User.query().filter(User.admin == true())\
898 return User.query().filter(User.admin == true())\
899 .order_by(User.username.asc()).all()
899 .order_by(User.username.asc()).all()
900
900
901 @classmethod
901 @classmethod
902 def get_default_user(cls, cache=False, refresh=False):
902 def get_default_user(cls, cache=False, refresh=False):
903 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
903 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
904 if user is None:
904 if user is None:
905 raise Exception('FATAL: Missing default account!')
905 raise Exception('FATAL: Missing default account!')
906 if refresh:
906 if refresh:
907 # The default user might be based on outdated state which
907 # The default user might be based on outdated state which
908 # has been loaded from the cache.
908 # has been loaded from the cache.
909 # A call to refresh() ensures that the
909 # A call to refresh() ensures that the
910 # latest state from the database is used.
910 # latest state from the database is used.
911 Session().refresh(user)
911 Session().refresh(user)
912 return user
912 return user
913
913
914 def _get_default_perms(self, user, suffix=''):
914 def _get_default_perms(self, user, suffix=''):
915 from rhodecode.model.permission import PermissionModel
915 from rhodecode.model.permission import PermissionModel
916 return PermissionModel().get_default_perms(user.user_perms, suffix)
916 return PermissionModel().get_default_perms(user.user_perms, suffix)
917
917
918 def get_default_perms(self, suffix=''):
918 def get_default_perms(self, suffix=''):
919 return self._get_default_perms(self, suffix)
919 return self._get_default_perms(self, suffix)
920
920
921 def get_api_data(self, include_secrets=False, details='full'):
921 def get_api_data(self, include_secrets=False, details='full'):
922 """
922 """
923 Common function for generating user related data for API
923 Common function for generating user related data for API
924
924
925 :param include_secrets: By default secrets in the API data will be replaced
925 :param include_secrets: By default secrets in the API data will be replaced
926 by a placeholder value to prevent exposing this data by accident. In case
926 by a placeholder value to prevent exposing this data by accident. In case
927 this data shall be exposed, set this flag to ``True``.
927 this data shall be exposed, set this flag to ``True``.
928
928
929 :param details: details can be 'basic|full' basic gives only a subset of
929 :param details: details can be 'basic|full' basic gives only a subset of
930 the available user information that includes user_id, name and emails.
930 the available user information that includes user_id, name and emails.
931 """
931 """
932 user = self
932 user = self
933 user_data = self.user_data
933 user_data = self.user_data
934 data = {
934 data = {
935 'user_id': user.user_id,
935 'user_id': user.user_id,
936 'username': user.username,
936 'username': user.username,
937 'firstname': user.name,
937 'firstname': user.name,
938 'lastname': user.lastname,
938 'lastname': user.lastname,
939 'email': user.email,
939 'email': user.email,
940 'emails': user.emails,
940 'emails': user.emails,
941 }
941 }
942 if details == 'basic':
942 if details == 'basic':
943 return data
943 return data
944
944
945 auth_token_length = 40
945 auth_token_length = 40
946 auth_token_replacement = '*' * auth_token_length
946 auth_token_replacement = '*' * auth_token_length
947
947
948 extras = {
948 extras = {
949 'auth_tokens': [auth_token_replacement],
949 'auth_tokens': [auth_token_replacement],
950 'active': user.active,
950 'active': user.active,
951 'admin': user.admin,
951 'admin': user.admin,
952 'extern_type': user.extern_type,
952 'extern_type': user.extern_type,
953 'extern_name': user.extern_name,
953 'extern_name': user.extern_name,
954 'last_login': user.last_login,
954 'last_login': user.last_login,
955 'last_activity': user.last_activity,
955 'last_activity': user.last_activity,
956 'ip_addresses': user.ip_addresses,
956 'ip_addresses': user.ip_addresses,
957 'language': user_data.get('language')
957 'language': user_data.get('language')
958 }
958 }
959 data.update(extras)
959 data.update(extras)
960
960
961 if include_secrets:
961 if include_secrets:
962 data['auth_tokens'] = user.auth_tokens
962 data['auth_tokens'] = user.auth_tokens
963 return data
963 return data
964
964
965 def __json__(self):
965 def __json__(self):
966 data = {
966 data = {
967 'full_name': self.full_name,
967 'full_name': self.full_name,
968 'full_name_or_username': self.full_name_or_username,
968 'full_name_or_username': self.full_name_or_username,
969 'short_contact': self.short_contact,
969 'short_contact': self.short_contact,
970 'full_contact': self.full_contact,
970 'full_contact': self.full_contact,
971 }
971 }
972 data.update(self.get_api_data())
972 data.update(self.get_api_data())
973 return data
973 return data
974
974
975
975
976 class UserApiKeys(Base, BaseModel):
976 class UserApiKeys(Base, BaseModel):
977 __tablename__ = 'user_api_keys'
977 __tablename__ = 'user_api_keys'
978 __table_args__ = (
978 __table_args__ = (
979 Index('uak_api_key_idx', 'api_key', unique=True),
979 Index('uak_api_key_idx', 'api_key', unique=True),
980 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
980 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
982 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
982 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
983 )
983 )
984 __mapper_args__ = {}
984 __mapper_args__ = {}
985
985
986 # ApiKey role
986 # ApiKey role
987 ROLE_ALL = 'token_role_all'
987 ROLE_ALL = 'token_role_all'
988 ROLE_HTTP = 'token_role_http'
988 ROLE_HTTP = 'token_role_http'
989 ROLE_VCS = 'token_role_vcs'
989 ROLE_VCS = 'token_role_vcs'
990 ROLE_API = 'token_role_api'
990 ROLE_API = 'token_role_api'
991 ROLE_FEED = 'token_role_feed'
991 ROLE_FEED = 'token_role_feed'
992 ROLE_PASSWORD_RESET = 'token_password_reset'
992 ROLE_PASSWORD_RESET = 'token_password_reset'
993
993
994 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
994 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
995
995
996 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
998 api_key = Column("api_key", String(255), nullable=False, unique=True)
998 api_key = Column("api_key", String(255), nullable=False, unique=True)
999 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
999 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1000 expires = Column('expires', Float(53), nullable=False)
1000 expires = Column('expires', Float(53), nullable=False)
1001 role = Column('role', String(255), nullable=True)
1001 role = Column('role', String(255), nullable=True)
1002 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1002 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1003
1003
1004 # scope columns
1004 # scope columns
1005 repo_id = Column(
1005 repo_id = Column(
1006 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1006 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1007 nullable=True, unique=None, default=None)
1007 nullable=True, unique=None, default=None)
1008 repo = relationship('Repository', lazy='joined')
1008 repo = relationship('Repository', lazy='joined')
1009
1009
1010 repo_group_id = Column(
1010 repo_group_id = Column(
1011 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1011 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1012 nullable=True, unique=None, default=None)
1012 nullable=True, unique=None, default=None)
1013 repo_group = relationship('RepoGroup', lazy='joined')
1013 repo_group = relationship('RepoGroup', lazy='joined')
1014
1014
1015 user = relationship('User', lazy='joined')
1015 user = relationship('User', lazy='joined')
1016
1016
1017 def __unicode__(self):
1017 def __unicode__(self):
1018 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1018 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1019
1019
1020 def __json__(self):
1020 def __json__(self):
1021 data = {
1021 data = {
1022 'auth_token': self.api_key,
1022 'auth_token': self.api_key,
1023 'role': self.role,
1023 'role': self.role,
1024 'scope': self.scope_humanized,
1024 'scope': self.scope_humanized,
1025 'expired': self.expired
1025 'expired': self.expired
1026 }
1026 }
1027 return data
1027 return data
1028
1028
1029 def get_api_data(self, include_secrets=False):
1029 def get_api_data(self, include_secrets=False):
1030 data = self.__json__()
1030 data = self.__json__()
1031 if include_secrets:
1031 if include_secrets:
1032 return data
1032 return data
1033 else:
1033 else:
1034 data['auth_token'] = self.token_obfuscated
1034 data['auth_token'] = self.token_obfuscated
1035 return data
1035 return data
1036
1036
1037 @hybrid_property
1037 @hybrid_property
1038 def description_safe(self):
1038 def description_safe(self):
1039 from rhodecode.lib import helpers as h
1039 from rhodecode.lib import helpers as h
1040 return h.escape(self.description)
1040 return h.escape(self.description)
1041
1041
1042 @property
1042 @property
1043 def expired(self):
1043 def expired(self):
1044 if self.expires == -1:
1044 if self.expires == -1:
1045 return False
1045 return False
1046 return time.time() > self.expires
1046 return time.time() > self.expires
1047
1047
1048 @classmethod
1048 @classmethod
1049 def _get_role_name(cls, role):
1049 def _get_role_name(cls, role):
1050 return {
1050 return {
1051 cls.ROLE_ALL: _('all'),
1051 cls.ROLE_ALL: _('all'),
1052 cls.ROLE_HTTP: _('http/web interface'),
1052 cls.ROLE_HTTP: _('http/web interface'),
1053 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1053 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1054 cls.ROLE_API: _('api calls'),
1054 cls.ROLE_API: _('api calls'),
1055 cls.ROLE_FEED: _('feed access'),
1055 cls.ROLE_FEED: _('feed access'),
1056 }.get(role, role)
1056 }.get(role, role)
1057
1057
1058 @property
1058 @property
1059 def role_humanized(self):
1059 def role_humanized(self):
1060 return self._get_role_name(self.role)
1060 return self._get_role_name(self.role)
1061
1061
1062 def _get_scope(self):
1062 def _get_scope(self):
1063 if self.repo:
1063 if self.repo:
1064 return repr(self.repo)
1064 return repr(self.repo)
1065 if self.repo_group:
1065 if self.repo_group:
1066 return repr(self.repo_group) + ' (recursive)'
1066 return repr(self.repo_group) + ' (recursive)'
1067 return 'global'
1067 return 'global'
1068
1068
1069 @property
1069 @property
1070 def scope_humanized(self):
1070 def scope_humanized(self):
1071 return self._get_scope()
1071 return self._get_scope()
1072
1072
1073 @property
1073 @property
1074 def token_obfuscated(self):
1074 def token_obfuscated(self):
1075 if self.api_key:
1075 if self.api_key:
1076 return self.api_key[:4] + "****"
1076 return self.api_key[:4] + "****"
1077
1077
1078
1078
1079 class UserEmailMap(Base, BaseModel):
1079 class UserEmailMap(Base, BaseModel):
1080 __tablename__ = 'user_email_map'
1080 __tablename__ = 'user_email_map'
1081 __table_args__ = (
1081 __table_args__ = (
1082 Index('uem_email_idx', 'email'),
1082 Index('uem_email_idx', 'email'),
1083 UniqueConstraint('email'),
1083 UniqueConstraint('email'),
1084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1086 )
1086 )
1087 __mapper_args__ = {}
1087 __mapper_args__ = {}
1088
1088
1089 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1089 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1090 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1090 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1091 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1091 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1092 user = relationship('User', lazy='joined')
1092 user = relationship('User', lazy='joined')
1093
1093
1094 @validates('_email')
1094 @validates('_email')
1095 def validate_email(self, key, email):
1095 def validate_email(self, key, email):
1096 # check if this email is not main one
1096 # check if this email is not main one
1097 main_email = Session().query(User).filter(User.email == email).scalar()
1097 main_email = Session().query(User).filter(User.email == email).scalar()
1098 if main_email is not None:
1098 if main_email is not None:
1099 raise AttributeError('email %s is present is user table' % email)
1099 raise AttributeError('email %s is present is user table' % email)
1100 return email
1100 return email
1101
1101
1102 @hybrid_property
1102 @hybrid_property
1103 def email(self):
1103 def email(self):
1104 return self._email
1104 return self._email
1105
1105
1106 @email.setter
1106 @email.setter
1107 def email(self, val):
1107 def email(self, val):
1108 self._email = val.lower() if val else None
1108 self._email = val.lower() if val else None
1109
1109
1110
1110
1111 class UserIpMap(Base, BaseModel):
1111 class UserIpMap(Base, BaseModel):
1112 __tablename__ = 'user_ip_map'
1112 __tablename__ = 'user_ip_map'
1113 __table_args__ = (
1113 __table_args__ = (
1114 UniqueConstraint('user_id', 'ip_addr'),
1114 UniqueConstraint('user_id', 'ip_addr'),
1115 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1115 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1116 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1116 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1117 )
1117 )
1118 __mapper_args__ = {}
1118 __mapper_args__ = {}
1119
1119
1120 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1120 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1121 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1121 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1122 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1122 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1123 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1123 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1124 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1124 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1125 user = relationship('User', lazy='joined')
1125 user = relationship('User', lazy='joined')
1126
1126
1127 @hybrid_property
1127 @hybrid_property
1128 def description_safe(self):
1128 def description_safe(self):
1129 from rhodecode.lib import helpers as h
1129 from rhodecode.lib import helpers as h
1130 return h.escape(self.description)
1130 return h.escape(self.description)
1131
1131
1132 @classmethod
1132 @classmethod
1133 def _get_ip_range(cls, ip_addr):
1133 def _get_ip_range(cls, ip_addr):
1134 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1134 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1135 return [str(net.network_address), str(net.broadcast_address)]
1135 return [str(net.network_address), str(net.broadcast_address)]
1136
1136
1137 def __json__(self):
1137 def __json__(self):
1138 return {
1138 return {
1139 'ip_addr': self.ip_addr,
1139 'ip_addr': self.ip_addr,
1140 'ip_range': self._get_ip_range(self.ip_addr),
1140 'ip_range': self._get_ip_range(self.ip_addr),
1141 }
1141 }
1142
1142
1143 def __unicode__(self):
1143 def __unicode__(self):
1144 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1144 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1145 self.user_id, self.ip_addr)
1145 self.user_id, self.ip_addr)
1146
1146
1147
1147
1148 class UserSshKeys(Base, BaseModel):
1148 class UserSshKeys(Base, BaseModel):
1149 __tablename__ = 'user_ssh_keys'
1149 __tablename__ = 'user_ssh_keys'
1150 __table_args__ = (
1150 __table_args__ = (
1151 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1151 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1152 UniqueConstraint('ssh_key_fingerprint'),
1152 UniqueConstraint('ssh_key_fingerprint'),
1153
1153
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1155 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1155 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1156 )
1156 )
1157 __mapper_args__ = {}
1157 __mapper_args__ = {}
1158
1158
1159 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1159 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1160 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1160 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1161 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1161 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1162
1162
1163 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1163 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1164
1164
1165 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1165 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1166 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1166 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1167 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1167 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1168
1168
1169 user = relationship('User', lazy='joined')
1169 user = relationship('User', lazy='joined')
1170
1170
1171 def __json__(self):
1171 def __json__(self):
1172 data = {
1172 data = {
1173 'ssh_fingerprint': self.ssh_key_fingerprint,
1173 'ssh_fingerprint': self.ssh_key_fingerprint,
1174 'description': self.description,
1174 'description': self.description,
1175 'created_on': self.created_on
1175 'created_on': self.created_on
1176 }
1176 }
1177 return data
1177 return data
1178
1178
1179 def get_api_data(self):
1179 def get_api_data(self):
1180 data = self.__json__()
1180 data = self.__json__()
1181 return data
1181 return data
1182
1182
1183
1183
1184 class UserLog(Base, BaseModel):
1184 class UserLog(Base, BaseModel):
1185 __tablename__ = 'user_logs'
1185 __tablename__ = 'user_logs'
1186 __table_args__ = (
1186 __table_args__ = (
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1189 )
1189 )
1190 VERSION_1 = 'v1'
1190 VERSION_1 = 'v1'
1191 VERSION_2 = 'v2'
1191 VERSION_2 = 'v2'
1192 VERSIONS = [VERSION_1, VERSION_2]
1192 VERSIONS = [VERSION_1, VERSION_2]
1193
1193
1194 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1194 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1195 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1195 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1196 username = Column("username", String(255), nullable=True, unique=None, default=None)
1196 username = Column("username", String(255), nullable=True, unique=None, default=None)
1197 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1197 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1198 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1198 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1199 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1199 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1200 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1200 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1201 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1201 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1202
1202
1203 version = Column("version", String(255), nullable=True, default=VERSION_1)
1203 version = Column("version", String(255), nullable=True, default=VERSION_1)
1204 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1204 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1205 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1205 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1206
1206
1207 def __unicode__(self):
1207 def __unicode__(self):
1208 return u"<%s('id:%s:%s')>" % (
1208 return u"<%s('id:%s:%s')>" % (
1209 self.__class__.__name__, self.repository_name, self.action)
1209 self.__class__.__name__, self.repository_name, self.action)
1210
1210
1211 def __json__(self):
1211 def __json__(self):
1212 return {
1212 return {
1213 'user_id': self.user_id,
1213 'user_id': self.user_id,
1214 'username': self.username,
1214 'username': self.username,
1215 'repository_id': self.repository_id,
1215 'repository_id': self.repository_id,
1216 'repository_name': self.repository_name,
1216 'repository_name': self.repository_name,
1217 'user_ip': self.user_ip,
1217 'user_ip': self.user_ip,
1218 'action_date': self.action_date,
1218 'action_date': self.action_date,
1219 'action': self.action,
1219 'action': self.action,
1220 }
1220 }
1221
1221
1222 @property
1222 @property
1223 def action_as_day(self):
1223 def action_as_day(self):
1224 return datetime.date(*self.action_date.timetuple()[:3])
1224 return datetime.date(*self.action_date.timetuple()[:3])
1225
1225
1226 user = relationship('User')
1226 user = relationship('User')
1227 repository = relationship('Repository', cascade='')
1227 repository = relationship('Repository', cascade='')
1228
1228
1229
1229
1230 class UserGroup(Base, BaseModel):
1230 class UserGroup(Base, BaseModel):
1231 __tablename__ = 'users_groups'
1231 __tablename__ = 'users_groups'
1232 __table_args__ = (
1232 __table_args__ = (
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1235 )
1235 )
1236
1236
1237 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1238 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1239 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1239 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1240 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1240 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1241 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1241 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1243 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1243 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1244 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1244 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1245
1245
1246 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1246 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1247 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1247 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1248 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1248 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1249 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1249 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1250 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1250 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1251 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1251 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1252
1252
1253 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1253 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1254
1254
1255 @classmethod
1255 @classmethod
1256 def _load_group_data(cls, column):
1256 def _load_group_data(cls, column):
1257 if not column:
1257 if not column:
1258 return {}
1258 return {}
1259
1259
1260 try:
1260 try:
1261 return json.loads(column) or {}
1261 return json.loads(column) or {}
1262 except TypeError:
1262 except TypeError:
1263 return {}
1263 return {}
1264
1264
1265 @hybrid_property
1265 @hybrid_property
1266 def description_safe(self):
1266 def description_safe(self):
1267 from rhodecode.lib import helpers as h
1267 from rhodecode.lib import helpers as h
1268 return h.escape(self.description)
1268 return h.escape(self.description)
1269
1269
1270 @hybrid_property
1270 @hybrid_property
1271 def group_data(self):
1271 def group_data(self):
1272 return self._load_group_data(self._group_data)
1272 return self._load_group_data(self._group_data)
1273
1273
1274 @group_data.expression
1274 @group_data.expression
1275 def group_data(self, **kwargs):
1275 def group_data(self, **kwargs):
1276 return self._group_data
1276 return self._group_data
1277
1277
1278 @group_data.setter
1278 @group_data.setter
1279 def group_data(self, val):
1279 def group_data(self, val):
1280 try:
1280 try:
1281 self._group_data = json.dumps(val)
1281 self._group_data = json.dumps(val)
1282 except Exception:
1282 except Exception:
1283 log.error(traceback.format_exc())
1283 log.error(traceback.format_exc())
1284
1284
1285 def __unicode__(self):
1285 def __unicode__(self):
1286 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1286 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1287 self.users_group_id,
1287 self.users_group_id,
1288 self.users_group_name)
1288 self.users_group_name)
1289
1289
1290 @classmethod
1290 @classmethod
1291 def get_by_group_name(cls, group_name, cache=False,
1291 def get_by_group_name(cls, group_name, cache=False,
1292 case_insensitive=False):
1292 case_insensitive=False):
1293 if case_insensitive:
1293 if case_insensitive:
1294 q = cls.query().filter(func.lower(cls.users_group_name) ==
1294 q = cls.query().filter(func.lower(cls.users_group_name) ==
1295 func.lower(group_name))
1295 func.lower(group_name))
1296
1296
1297 else:
1297 else:
1298 q = cls.query().filter(cls.users_group_name == group_name)
1298 q = cls.query().filter(cls.users_group_name == group_name)
1299 if cache:
1299 if cache:
1300 q = q.options(
1300 q = q.options(
1301 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1301 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1302 return q.scalar()
1302 return q.scalar()
1303
1303
1304 @classmethod
1304 @classmethod
1305 def get(cls, user_group_id, cache=False):
1305 def get(cls, user_group_id, cache=False):
1306 user_group = cls.query()
1306 user_group = cls.query()
1307 if cache:
1307 if cache:
1308 user_group = user_group.options(
1308 user_group = user_group.options(
1309 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1309 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1310 return user_group.get(user_group_id)
1310 return user_group.get(user_group_id)
1311
1311
1312 def permissions(self, with_admins=True, with_owner=True):
1312 def permissions(self, with_admins=True, with_owner=True):
1313 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1313 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1314 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1314 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1315 joinedload(UserUserGroupToPerm.user),
1315 joinedload(UserUserGroupToPerm.user),
1316 joinedload(UserUserGroupToPerm.permission),)
1316 joinedload(UserUserGroupToPerm.permission),)
1317
1317
1318 # get owners and admins and permissions. We do a trick of re-writing
1318 # get owners and admins and permissions. We do a trick of re-writing
1319 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1319 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1320 # has a global reference and changing one object propagates to all
1320 # has a global reference and changing one object propagates to all
1321 # others. This means if admin is also an owner admin_row that change
1321 # others. This means if admin is also an owner admin_row that change
1322 # would propagate to both objects
1322 # would propagate to both objects
1323 perm_rows = []
1323 perm_rows = []
1324 for _usr in q.all():
1324 for _usr in q.all():
1325 usr = AttributeDict(_usr.user.get_dict())
1325 usr = AttributeDict(_usr.user.get_dict())
1326 usr.permission = _usr.permission.permission_name
1326 usr.permission = _usr.permission.permission_name
1327 perm_rows.append(usr)
1327 perm_rows.append(usr)
1328
1328
1329 # filter the perm rows by 'default' first and then sort them by
1329 # filter the perm rows by 'default' first and then sort them by
1330 # admin,write,read,none permissions sorted again alphabetically in
1330 # admin,write,read,none permissions sorted again alphabetically in
1331 # each group
1331 # each group
1332 perm_rows = sorted(perm_rows, key=display_sort)
1332 perm_rows = sorted(perm_rows, key=display_sort)
1333
1333
1334 _admin_perm = 'usergroup.admin'
1334 _admin_perm = 'usergroup.admin'
1335 owner_row = []
1335 owner_row = []
1336 if with_owner:
1336 if with_owner:
1337 usr = AttributeDict(self.user.get_dict())
1337 usr = AttributeDict(self.user.get_dict())
1338 usr.owner_row = True
1338 usr.owner_row = True
1339 usr.permission = _admin_perm
1339 usr.permission = _admin_perm
1340 owner_row.append(usr)
1340 owner_row.append(usr)
1341
1341
1342 super_admin_rows = []
1342 super_admin_rows = []
1343 if with_admins:
1343 if with_admins:
1344 for usr in User.get_all_super_admins():
1344 for usr in User.get_all_super_admins():
1345 # if this admin is also owner, don't double the record
1345 # if this admin is also owner, don't double the record
1346 if usr.user_id == owner_row[0].user_id:
1346 if usr.user_id == owner_row[0].user_id:
1347 owner_row[0].admin_row = True
1347 owner_row[0].admin_row = True
1348 else:
1348 else:
1349 usr = AttributeDict(usr.get_dict())
1349 usr = AttributeDict(usr.get_dict())
1350 usr.admin_row = True
1350 usr.admin_row = True
1351 usr.permission = _admin_perm
1351 usr.permission = _admin_perm
1352 super_admin_rows.append(usr)
1352 super_admin_rows.append(usr)
1353
1353
1354 return super_admin_rows + owner_row + perm_rows
1354 return super_admin_rows + owner_row + perm_rows
1355
1355
1356 def permission_user_groups(self):
1356 def permission_user_groups(self):
1357 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1357 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1358 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1358 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1359 joinedload(UserGroupUserGroupToPerm.target_user_group),
1359 joinedload(UserGroupUserGroupToPerm.target_user_group),
1360 joinedload(UserGroupUserGroupToPerm.permission),)
1360 joinedload(UserGroupUserGroupToPerm.permission),)
1361
1361
1362 perm_rows = []
1362 perm_rows = []
1363 for _user_group in q.all():
1363 for _user_group in q.all():
1364 usr = AttributeDict(_user_group.user_group.get_dict())
1364 usr = AttributeDict(_user_group.user_group.get_dict())
1365 usr.permission = _user_group.permission.permission_name
1365 usr.permission = _user_group.permission.permission_name
1366 perm_rows.append(usr)
1366 perm_rows.append(usr)
1367
1367
1368 return perm_rows
1368 return perm_rows
1369
1369
1370 def _get_default_perms(self, user_group, suffix=''):
1370 def _get_default_perms(self, user_group, suffix=''):
1371 from rhodecode.model.permission import PermissionModel
1371 from rhodecode.model.permission import PermissionModel
1372 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1372 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1373
1373
1374 def get_default_perms(self, suffix=''):
1374 def get_default_perms(self, suffix=''):
1375 return self._get_default_perms(self, suffix)
1375 return self._get_default_perms(self, suffix)
1376
1376
1377 def get_api_data(self, with_group_members=True, include_secrets=False):
1377 def get_api_data(self, with_group_members=True, include_secrets=False):
1378 """
1378 """
1379 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1379 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1380 basically forwarded.
1380 basically forwarded.
1381
1381
1382 """
1382 """
1383 user_group = self
1383 user_group = self
1384 data = {
1384 data = {
1385 'users_group_id': user_group.users_group_id,
1385 'users_group_id': user_group.users_group_id,
1386 'group_name': user_group.users_group_name,
1386 'group_name': user_group.users_group_name,
1387 'group_description': user_group.user_group_description,
1387 'group_description': user_group.user_group_description,
1388 'active': user_group.users_group_active,
1388 'active': user_group.users_group_active,
1389 'owner': user_group.user.username,
1389 'owner': user_group.user.username,
1390 'owner_email': user_group.user.email,
1390 'owner_email': user_group.user.email,
1391 }
1391 }
1392
1392
1393 if with_group_members:
1393 if with_group_members:
1394 users = []
1394 users = []
1395 for user in user_group.members:
1395 for user in user_group.members:
1396 user = user.user
1396 user = user.user
1397 users.append(user.get_api_data(include_secrets=include_secrets))
1397 users.append(user.get_api_data(include_secrets=include_secrets))
1398 data['users'] = users
1398 data['users'] = users
1399
1399
1400 return data
1400 return data
1401
1401
1402
1402
1403 class UserGroupMember(Base, BaseModel):
1403 class UserGroupMember(Base, BaseModel):
1404 __tablename__ = 'users_groups_members'
1404 __tablename__ = 'users_groups_members'
1405 __table_args__ = (
1405 __table_args__ = (
1406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1407 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1407 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1408 )
1408 )
1409
1409
1410 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1410 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1411 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1411 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1413
1413
1414 user = relationship('User', lazy='joined')
1414 user = relationship('User', lazy='joined')
1415 users_group = relationship('UserGroup')
1415 users_group = relationship('UserGroup')
1416
1416
1417 def __init__(self, gr_id='', u_id=''):
1417 def __init__(self, gr_id='', u_id=''):
1418 self.users_group_id = gr_id
1418 self.users_group_id = gr_id
1419 self.user_id = u_id
1419 self.user_id = u_id
1420
1420
1421
1421
1422 class RepositoryField(Base, BaseModel):
1422 class RepositoryField(Base, BaseModel):
1423 __tablename__ = 'repositories_fields'
1423 __tablename__ = 'repositories_fields'
1424 __table_args__ = (
1424 __table_args__ = (
1425 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1425 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1426 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1426 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1427 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1427 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1428 )
1428 )
1429 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1429 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1430
1430
1431 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1431 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1432 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1432 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1433 field_key = Column("field_key", String(250))
1433 field_key = Column("field_key", String(250))
1434 field_label = Column("field_label", String(1024), nullable=False)
1434 field_label = Column("field_label", String(1024), nullable=False)
1435 field_value = Column("field_value", String(10000), nullable=False)
1435 field_value = Column("field_value", String(10000), nullable=False)
1436 field_desc = Column("field_desc", String(1024), nullable=False)
1436 field_desc = Column("field_desc", String(1024), nullable=False)
1437 field_type = Column("field_type", String(255), nullable=False, unique=None)
1437 field_type = Column("field_type", String(255), nullable=False, unique=None)
1438 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1438 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1439
1439
1440 repository = relationship('Repository')
1440 repository = relationship('Repository')
1441
1441
1442 @property
1442 @property
1443 def field_key_prefixed(self):
1443 def field_key_prefixed(self):
1444 return 'ex_%s' % self.field_key
1444 return 'ex_%s' % self.field_key
1445
1445
1446 @classmethod
1446 @classmethod
1447 def un_prefix_key(cls, key):
1447 def un_prefix_key(cls, key):
1448 if key.startswith(cls.PREFIX):
1448 if key.startswith(cls.PREFIX):
1449 return key[len(cls.PREFIX):]
1449 return key[len(cls.PREFIX):]
1450 return key
1450 return key
1451
1451
1452 @classmethod
1452 @classmethod
1453 def get_by_key_name(cls, key, repo):
1453 def get_by_key_name(cls, key, repo):
1454 row = cls.query()\
1454 row = cls.query()\
1455 .filter(cls.repository == repo)\
1455 .filter(cls.repository == repo)\
1456 .filter(cls.field_key == key).scalar()
1456 .filter(cls.field_key == key).scalar()
1457 return row
1457 return row
1458
1458
1459
1459
1460 class Repository(Base, BaseModel):
1460 class Repository(Base, BaseModel):
1461 __tablename__ = 'repositories'
1461 __tablename__ = 'repositories'
1462 __table_args__ = (
1462 __table_args__ = (
1463 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1463 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1466 )
1466 )
1467 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1467 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1468 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1468 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1469
1469
1470 STATE_CREATED = 'repo_state_created'
1470 STATE_CREATED = 'repo_state_created'
1471 STATE_PENDING = 'repo_state_pending'
1471 STATE_PENDING = 'repo_state_pending'
1472 STATE_ERROR = 'repo_state_error'
1472 STATE_ERROR = 'repo_state_error'
1473
1473
1474 LOCK_AUTOMATIC = 'lock_auto'
1474 LOCK_AUTOMATIC = 'lock_auto'
1475 LOCK_API = 'lock_api'
1475 LOCK_API = 'lock_api'
1476 LOCK_WEB = 'lock_web'
1476 LOCK_WEB = 'lock_web'
1477 LOCK_PULL = 'lock_pull'
1477 LOCK_PULL = 'lock_pull'
1478
1478
1479 NAME_SEP = URL_SEP
1479 NAME_SEP = URL_SEP
1480
1480
1481 repo_id = Column(
1481 repo_id = Column(
1482 "repo_id", Integer(), nullable=False, unique=True, default=None,
1482 "repo_id", Integer(), nullable=False, unique=True, default=None,
1483 primary_key=True)
1483 primary_key=True)
1484 _repo_name = Column(
1484 _repo_name = Column(
1485 "repo_name", Text(), nullable=False, default=None)
1485 "repo_name", Text(), nullable=False, default=None)
1486 _repo_name_hash = Column(
1486 _repo_name_hash = Column(
1487 "repo_name_hash", String(255), nullable=False, unique=True)
1487 "repo_name_hash", String(255), nullable=False, unique=True)
1488 repo_state = Column("repo_state", String(255), nullable=True)
1488 repo_state = Column("repo_state", String(255), nullable=True)
1489
1489
1490 clone_uri = Column(
1490 clone_uri = Column(
1491 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1491 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1492 default=None)
1492 default=None)
1493 repo_type = Column(
1493 repo_type = Column(
1494 "repo_type", String(255), nullable=False, unique=False, default=None)
1494 "repo_type", String(255), nullable=False, unique=False, default=None)
1495 user_id = Column(
1495 user_id = Column(
1496 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1496 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1497 unique=False, default=None)
1497 unique=False, default=None)
1498 private = Column(
1498 private = Column(
1499 "private", Boolean(), nullable=True, unique=None, default=None)
1499 "private", Boolean(), nullable=True, unique=None, default=None)
1500 enable_statistics = Column(
1500 enable_statistics = Column(
1501 "statistics", Boolean(), nullable=True, unique=None, default=True)
1501 "statistics", Boolean(), nullable=True, unique=None, default=True)
1502 enable_downloads = Column(
1502 enable_downloads = Column(
1503 "downloads", Boolean(), nullable=True, unique=None, default=True)
1503 "downloads", Boolean(), nullable=True, unique=None, default=True)
1504 description = Column(
1504 description = Column(
1505 "description", String(10000), nullable=True, unique=None, default=None)
1505 "description", String(10000), nullable=True, unique=None, default=None)
1506 created_on = Column(
1506 created_on = Column(
1507 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1507 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1508 default=datetime.datetime.now)
1508 default=datetime.datetime.now)
1509 updated_on = Column(
1509 updated_on = Column(
1510 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1510 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1511 default=datetime.datetime.now)
1511 default=datetime.datetime.now)
1512 _landing_revision = Column(
1512 _landing_revision = Column(
1513 "landing_revision", String(255), nullable=False, unique=False,
1513 "landing_revision", String(255), nullable=False, unique=False,
1514 default=None)
1514 default=None)
1515 enable_locking = Column(
1515 enable_locking = Column(
1516 "enable_locking", Boolean(), nullable=False, unique=None,
1516 "enable_locking", Boolean(), nullable=False, unique=None,
1517 default=False)
1517 default=False)
1518 _locked = Column(
1518 _locked = Column(
1519 "locked", String(255), nullable=True, unique=False, default=None)
1519 "locked", String(255), nullable=True, unique=False, default=None)
1520 _changeset_cache = Column(
1520 _changeset_cache = Column(
1521 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1521 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1522
1522
1523 fork_id = Column(
1523 fork_id = Column(
1524 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1524 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1525 nullable=True, unique=False, default=None)
1525 nullable=True, unique=False, default=None)
1526 group_id = Column(
1526 group_id = Column(
1527 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1527 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1528 unique=False, default=None)
1528 unique=False, default=None)
1529
1529
1530 user = relationship('User', lazy='joined')
1530 user = relationship('User', lazy='joined')
1531 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1531 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1532 group = relationship('RepoGroup', lazy='joined')
1532 group = relationship('RepoGroup', lazy='joined')
1533 repo_to_perm = relationship(
1533 repo_to_perm = relationship(
1534 'UserRepoToPerm', cascade='all',
1534 'UserRepoToPerm', cascade='all',
1535 order_by='UserRepoToPerm.repo_to_perm_id')
1535 order_by='UserRepoToPerm.repo_to_perm_id')
1536 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1536 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1537 stats = relationship('Statistics', cascade='all', uselist=False)
1537 stats = relationship('Statistics', cascade='all', uselist=False)
1538
1538
1539 followers = relationship(
1539 followers = relationship(
1540 'UserFollowing',
1540 'UserFollowing',
1541 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1541 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1542 cascade='all')
1542 cascade='all')
1543 extra_fields = relationship(
1543 extra_fields = relationship(
1544 'RepositoryField', cascade="all, delete, delete-orphan")
1544 'RepositoryField', cascade="all, delete, delete-orphan")
1545 logs = relationship('UserLog')
1545 logs = relationship('UserLog')
1546 comments = relationship(
1546 comments = relationship(
1547 'ChangesetComment', cascade="all, delete, delete-orphan")
1547 'ChangesetComment', cascade="all, delete, delete-orphan")
1548 pull_requests_source = relationship(
1548 pull_requests_source = relationship(
1549 'PullRequest',
1549 'PullRequest',
1550 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1550 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1551 cascade="all, delete, delete-orphan")
1551 cascade="all, delete, delete-orphan")
1552 pull_requests_target = relationship(
1552 pull_requests_target = relationship(
1553 'PullRequest',
1553 'PullRequest',
1554 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1554 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1555 cascade="all, delete, delete-orphan")
1555 cascade="all, delete, delete-orphan")
1556 ui = relationship('RepoRhodeCodeUi', cascade="all")
1556 ui = relationship('RepoRhodeCodeUi', cascade="all")
1557 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1557 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1558 integrations = relationship('Integration',
1558 integrations = relationship('Integration',
1559 cascade="all, delete, delete-orphan")
1559 cascade="all, delete, delete-orphan")
1560
1560
1561 def __unicode__(self):
1561 def __unicode__(self):
1562 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1562 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1563 safe_unicode(self.repo_name))
1563 safe_unicode(self.repo_name))
1564
1564
1565 @hybrid_property
1565 @hybrid_property
1566 def description_safe(self):
1566 def description_safe(self):
1567 from rhodecode.lib import helpers as h
1567 from rhodecode.lib import helpers as h
1568 return h.escape(self.description)
1568 return h.escape(self.description)
1569
1569
1570 @hybrid_property
1570 @hybrid_property
1571 def landing_rev(self):
1571 def landing_rev(self):
1572 # always should return [rev_type, rev]
1572 # always should return [rev_type, rev]
1573 if self._landing_revision:
1573 if self._landing_revision:
1574 _rev_info = self._landing_revision.split(':')
1574 _rev_info = self._landing_revision.split(':')
1575 if len(_rev_info) < 2:
1575 if len(_rev_info) < 2:
1576 _rev_info.insert(0, 'rev')
1576 _rev_info.insert(0, 'rev')
1577 return [_rev_info[0], _rev_info[1]]
1577 return [_rev_info[0], _rev_info[1]]
1578 return [None, None]
1578 return [None, None]
1579
1579
1580 @landing_rev.setter
1580 @landing_rev.setter
1581 def landing_rev(self, val):
1581 def landing_rev(self, val):
1582 if ':' not in val:
1582 if ':' not in val:
1583 raise ValueError('value must be delimited with `:` and consist '
1583 raise ValueError('value must be delimited with `:` and consist '
1584 'of <rev_type>:<rev>, got %s instead' % val)
1584 'of <rev_type>:<rev>, got %s instead' % val)
1585 self._landing_revision = val
1585 self._landing_revision = val
1586
1586
1587 @hybrid_property
1587 @hybrid_property
1588 def locked(self):
1588 def locked(self):
1589 if self._locked:
1589 if self._locked:
1590 user_id, timelocked, reason = self._locked.split(':')
1590 user_id, timelocked, reason = self._locked.split(':')
1591 lock_values = int(user_id), timelocked, reason
1591 lock_values = int(user_id), timelocked, reason
1592 else:
1592 else:
1593 lock_values = [None, None, None]
1593 lock_values = [None, None, None]
1594 return lock_values
1594 return lock_values
1595
1595
1596 @locked.setter
1596 @locked.setter
1597 def locked(self, val):
1597 def locked(self, val):
1598 if val and isinstance(val, (list, tuple)):
1598 if val and isinstance(val, (list, tuple)):
1599 self._locked = ':'.join(map(str, val))
1599 self._locked = ':'.join(map(str, val))
1600 else:
1600 else:
1601 self._locked = None
1601 self._locked = None
1602
1602
1603 @hybrid_property
1603 @hybrid_property
1604 def changeset_cache(self):
1604 def changeset_cache(self):
1605 from rhodecode.lib.vcs.backends.base import EmptyCommit
1605 from rhodecode.lib.vcs.backends.base import EmptyCommit
1606 dummy = EmptyCommit().__json__()
1606 dummy = EmptyCommit().__json__()
1607 if not self._changeset_cache:
1607 if not self._changeset_cache:
1608 return dummy
1608 return dummy
1609 try:
1609 try:
1610 return json.loads(self._changeset_cache)
1610 return json.loads(self._changeset_cache)
1611 except TypeError:
1611 except TypeError:
1612 return dummy
1612 return dummy
1613 except Exception:
1613 except Exception:
1614 log.error(traceback.format_exc())
1614 log.error(traceback.format_exc())
1615 return dummy
1615 return dummy
1616
1616
1617 @changeset_cache.setter
1617 @changeset_cache.setter
1618 def changeset_cache(self, val):
1618 def changeset_cache(self, val):
1619 try:
1619 try:
1620 self._changeset_cache = json.dumps(val)
1620 self._changeset_cache = json.dumps(val)
1621 except Exception:
1621 except Exception:
1622 log.error(traceback.format_exc())
1622 log.error(traceback.format_exc())
1623
1623
1624 @hybrid_property
1624 @hybrid_property
1625 def repo_name(self):
1625 def repo_name(self):
1626 return self._repo_name
1626 return self._repo_name
1627
1627
1628 @repo_name.setter
1628 @repo_name.setter
1629 def repo_name(self, value):
1629 def repo_name(self, value):
1630 self._repo_name = value
1630 self._repo_name = value
1631 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1631 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1632
1632
1633 @classmethod
1633 @classmethod
1634 def normalize_repo_name(cls, repo_name):
1634 def normalize_repo_name(cls, repo_name):
1635 """
1635 """
1636 Normalizes os specific repo_name to the format internally stored inside
1636 Normalizes os specific repo_name to the format internally stored inside
1637 database using URL_SEP
1637 database using URL_SEP
1638
1638
1639 :param cls:
1639 :param cls:
1640 :param repo_name:
1640 :param repo_name:
1641 """
1641 """
1642 return cls.NAME_SEP.join(repo_name.split(os.sep))
1642 return cls.NAME_SEP.join(repo_name.split(os.sep))
1643
1643
1644 @classmethod
1644 @classmethod
1645 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1645 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1646 session = Session()
1646 session = Session()
1647 q = session.query(cls).filter(cls.repo_name == repo_name)
1647 q = session.query(cls).filter(cls.repo_name == repo_name)
1648
1648
1649 if cache:
1649 if cache:
1650 if identity_cache:
1650 if identity_cache:
1651 val = cls.identity_cache(session, 'repo_name', repo_name)
1651 val = cls.identity_cache(session, 'repo_name', repo_name)
1652 if val:
1652 if val:
1653 return val
1653 return val
1654 else:
1654 else:
1655 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1655 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1656 q = q.options(
1656 q = q.options(
1657 FromCache("sql_cache_short", cache_key))
1657 FromCache("sql_cache_short", cache_key))
1658
1658
1659 return q.scalar()
1659 return q.scalar()
1660
1660
1661 @classmethod
1661 @classmethod
1662 def get_by_full_path(cls, repo_full_path):
1662 def get_by_full_path(cls, repo_full_path):
1663 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1663 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1664 repo_name = cls.normalize_repo_name(repo_name)
1664 repo_name = cls.normalize_repo_name(repo_name)
1665 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1665 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1666
1666
1667 @classmethod
1667 @classmethod
1668 def get_repo_forks(cls, repo_id):
1668 def get_repo_forks(cls, repo_id):
1669 return cls.query().filter(Repository.fork_id == repo_id)
1669 return cls.query().filter(Repository.fork_id == repo_id)
1670
1670
1671 @classmethod
1671 @classmethod
1672 def base_path(cls):
1672 def base_path(cls):
1673 """
1673 """
1674 Returns base path when all repos are stored
1674 Returns base path when all repos are stored
1675
1675
1676 :param cls:
1676 :param cls:
1677 """
1677 """
1678 q = Session().query(RhodeCodeUi)\
1678 q = Session().query(RhodeCodeUi)\
1679 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1679 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1680 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1680 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1681 return q.one().ui_value
1681 return q.one().ui_value
1682
1682
1683 @classmethod
1683 @classmethod
1684 def is_valid(cls, repo_name):
1684 def is_valid(cls, repo_name):
1685 """
1685 """
1686 returns True if given repo name is a valid filesystem repository
1686 returns True if given repo name is a valid filesystem repository
1687
1687
1688 :param cls:
1688 :param cls:
1689 :param repo_name:
1689 :param repo_name:
1690 """
1690 """
1691 from rhodecode.lib.utils import is_valid_repo
1691 from rhodecode.lib.utils import is_valid_repo
1692
1692
1693 return is_valid_repo(repo_name, cls.base_path())
1693 return is_valid_repo(repo_name, cls.base_path())
1694
1694
1695 @classmethod
1695 @classmethod
1696 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1696 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1697 case_insensitive=True):
1697 case_insensitive=True):
1698 q = Repository.query()
1698 q = Repository.query()
1699
1699
1700 if not isinstance(user_id, Optional):
1700 if not isinstance(user_id, Optional):
1701 q = q.filter(Repository.user_id == user_id)
1701 q = q.filter(Repository.user_id == user_id)
1702
1702
1703 if not isinstance(group_id, Optional):
1703 if not isinstance(group_id, Optional):
1704 q = q.filter(Repository.group_id == group_id)
1704 q = q.filter(Repository.group_id == group_id)
1705
1705
1706 if case_insensitive:
1706 if case_insensitive:
1707 q = q.order_by(func.lower(Repository.repo_name))
1707 q = q.order_by(func.lower(Repository.repo_name))
1708 else:
1708 else:
1709 q = q.order_by(Repository.repo_name)
1709 q = q.order_by(Repository.repo_name)
1710 return q.all()
1710 return q.all()
1711
1711
1712 @property
1712 @property
1713 def forks(self):
1713 def forks(self):
1714 """
1714 """
1715 Return forks of this repo
1715 Return forks of this repo
1716 """
1716 """
1717 return Repository.get_repo_forks(self.repo_id)
1717 return Repository.get_repo_forks(self.repo_id)
1718
1718
1719 @property
1719 @property
1720 def parent(self):
1720 def parent(self):
1721 """
1721 """
1722 Returns fork parent
1722 Returns fork parent
1723 """
1723 """
1724 return self.fork
1724 return self.fork
1725
1725
1726 @property
1726 @property
1727 def just_name(self):
1727 def just_name(self):
1728 return self.repo_name.split(self.NAME_SEP)[-1]
1728 return self.repo_name.split(self.NAME_SEP)[-1]
1729
1729
1730 @property
1730 @property
1731 def groups_with_parents(self):
1731 def groups_with_parents(self):
1732 groups = []
1732 groups = []
1733 if self.group is None:
1733 if self.group is None:
1734 return groups
1734 return groups
1735
1735
1736 cur_gr = self.group
1736 cur_gr = self.group
1737 groups.insert(0, cur_gr)
1737 groups.insert(0, cur_gr)
1738 while 1:
1738 while 1:
1739 gr = getattr(cur_gr, 'parent_group', None)
1739 gr = getattr(cur_gr, 'parent_group', None)
1740 cur_gr = cur_gr.parent_group
1740 cur_gr = cur_gr.parent_group
1741 if gr is None:
1741 if gr is None:
1742 break
1742 break
1743 groups.insert(0, gr)
1743 groups.insert(0, gr)
1744
1744
1745 return groups
1745 return groups
1746
1746
1747 @property
1747 @property
1748 def groups_and_repo(self):
1748 def groups_and_repo(self):
1749 return self.groups_with_parents, self
1749 return self.groups_with_parents, self
1750
1750
1751 @LazyProperty
1751 @LazyProperty
1752 def repo_path(self):
1752 def repo_path(self):
1753 """
1753 """
1754 Returns base full path for that repository means where it actually
1754 Returns base full path for that repository means where it actually
1755 exists on a filesystem
1755 exists on a filesystem
1756 """
1756 """
1757 q = Session().query(RhodeCodeUi).filter(
1757 q = Session().query(RhodeCodeUi).filter(
1758 RhodeCodeUi.ui_key == self.NAME_SEP)
1758 RhodeCodeUi.ui_key == self.NAME_SEP)
1759 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1759 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1760 return q.one().ui_value
1760 return q.one().ui_value
1761
1761
1762 @property
1762 @property
1763 def repo_full_path(self):
1763 def repo_full_path(self):
1764 p = [self.repo_path]
1764 p = [self.repo_path]
1765 # we need to split the name by / since this is how we store the
1765 # we need to split the name by / since this is how we store the
1766 # names in the database, but that eventually needs to be converted
1766 # names in the database, but that eventually needs to be converted
1767 # into a valid system path
1767 # into a valid system path
1768 p += self.repo_name.split(self.NAME_SEP)
1768 p += self.repo_name.split(self.NAME_SEP)
1769 return os.path.join(*map(safe_unicode, p))
1769 return os.path.join(*map(safe_unicode, p))
1770
1770
1771 @property
1771 @property
1772 def cache_keys(self):
1772 def cache_keys(self):
1773 """
1773 """
1774 Returns associated cache keys for that repo
1774 Returns associated cache keys for that repo
1775 """
1775 """
1776 return CacheKey.query()\
1776 return CacheKey.query()\
1777 .filter(CacheKey.cache_args == self.repo_name)\
1777 .filter(CacheKey.cache_args == self.repo_name)\
1778 .order_by(CacheKey.cache_key)\
1778 .order_by(CacheKey.cache_key)\
1779 .all()
1779 .all()
1780
1780
1781 def get_new_name(self, repo_name):
1781 def get_new_name(self, repo_name):
1782 """
1782 """
1783 returns new full repository name based on assigned group and new new
1783 returns new full repository name based on assigned group and new new
1784
1784
1785 :param group_name:
1785 :param group_name:
1786 """
1786 """
1787 path_prefix = self.group.full_path_splitted if self.group else []
1787 path_prefix = self.group.full_path_splitted if self.group else []
1788 return self.NAME_SEP.join(path_prefix + [repo_name])
1788 return self.NAME_SEP.join(path_prefix + [repo_name])
1789
1789
1790 @property
1790 @property
1791 def _config(self):
1791 def _config(self):
1792 """
1792 """
1793 Returns db based config object.
1793 Returns db based config object.
1794 """
1794 """
1795 from rhodecode.lib.utils import make_db_config
1795 from rhodecode.lib.utils import make_db_config
1796 return make_db_config(clear_session=False, repo=self)
1796 return make_db_config(clear_session=False, repo=self)
1797
1797
1798 def permissions(self, with_admins=True, with_owner=True):
1798 def permissions(self, with_admins=True, with_owner=True):
1799 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1799 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1800 q = q.options(joinedload(UserRepoToPerm.repository),
1800 q = q.options(joinedload(UserRepoToPerm.repository),
1801 joinedload(UserRepoToPerm.user),
1801 joinedload(UserRepoToPerm.user),
1802 joinedload(UserRepoToPerm.permission),)
1802 joinedload(UserRepoToPerm.permission),)
1803
1803
1804 # get owners and admins and permissions. We do a trick of re-writing
1804 # get owners and admins and permissions. We do a trick of re-writing
1805 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1805 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1806 # has a global reference and changing one object propagates to all
1806 # has a global reference and changing one object propagates to all
1807 # others. This means if admin is also an owner admin_row that change
1807 # others. This means if admin is also an owner admin_row that change
1808 # would propagate to both objects
1808 # would propagate to both objects
1809 perm_rows = []
1809 perm_rows = []
1810 for _usr in q.all():
1810 for _usr in q.all():
1811 usr = AttributeDict(_usr.user.get_dict())
1811 usr = AttributeDict(_usr.user.get_dict())
1812 usr.permission = _usr.permission.permission_name
1812 usr.permission = _usr.permission.permission_name
1813 perm_rows.append(usr)
1813 perm_rows.append(usr)
1814
1814
1815 # filter the perm rows by 'default' first and then sort them by
1815 # filter the perm rows by 'default' first and then sort them by
1816 # admin,write,read,none permissions sorted again alphabetically in
1816 # admin,write,read,none permissions sorted again alphabetically in
1817 # each group
1817 # each group
1818 perm_rows = sorted(perm_rows, key=display_sort)
1818 perm_rows = sorted(perm_rows, key=display_sort)
1819
1819
1820 _admin_perm = 'repository.admin'
1820 _admin_perm = 'repository.admin'
1821 owner_row = []
1821 owner_row = []
1822 if with_owner:
1822 if with_owner:
1823 usr = AttributeDict(self.user.get_dict())
1823 usr = AttributeDict(self.user.get_dict())
1824 usr.owner_row = True
1824 usr.owner_row = True
1825 usr.permission = _admin_perm
1825 usr.permission = _admin_perm
1826 owner_row.append(usr)
1826 owner_row.append(usr)
1827
1827
1828 super_admin_rows = []
1828 super_admin_rows = []
1829 if with_admins:
1829 if with_admins:
1830 for usr in User.get_all_super_admins():
1830 for usr in User.get_all_super_admins():
1831 # if this admin is also owner, don't double the record
1831 # if this admin is also owner, don't double the record
1832 if usr.user_id == owner_row[0].user_id:
1832 if usr.user_id == owner_row[0].user_id:
1833 owner_row[0].admin_row = True
1833 owner_row[0].admin_row = True
1834 else:
1834 else:
1835 usr = AttributeDict(usr.get_dict())
1835 usr = AttributeDict(usr.get_dict())
1836 usr.admin_row = True
1836 usr.admin_row = True
1837 usr.permission = _admin_perm
1837 usr.permission = _admin_perm
1838 super_admin_rows.append(usr)
1838 super_admin_rows.append(usr)
1839
1839
1840 return super_admin_rows + owner_row + perm_rows
1840 return super_admin_rows + owner_row + perm_rows
1841
1841
1842 def permission_user_groups(self):
1842 def permission_user_groups(self):
1843 q = UserGroupRepoToPerm.query().filter(
1843 q = UserGroupRepoToPerm.query().filter(
1844 UserGroupRepoToPerm.repository == self)
1844 UserGroupRepoToPerm.repository == self)
1845 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1845 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1846 joinedload(UserGroupRepoToPerm.users_group),
1846 joinedload(UserGroupRepoToPerm.users_group),
1847 joinedload(UserGroupRepoToPerm.permission),)
1847 joinedload(UserGroupRepoToPerm.permission),)
1848
1848
1849 perm_rows = []
1849 perm_rows = []
1850 for _user_group in q.all():
1850 for _user_group in q.all():
1851 usr = AttributeDict(_user_group.users_group.get_dict())
1851 usr = AttributeDict(_user_group.users_group.get_dict())
1852 usr.permission = _user_group.permission.permission_name
1852 usr.permission = _user_group.permission.permission_name
1853 perm_rows.append(usr)
1853 perm_rows.append(usr)
1854
1854
1855 return perm_rows
1855 return perm_rows
1856
1856
1857 def get_api_data(self, include_secrets=False):
1857 def get_api_data(self, include_secrets=False):
1858 """
1858 """
1859 Common function for generating repo api data
1859 Common function for generating repo api data
1860
1860
1861 :param include_secrets: See :meth:`User.get_api_data`.
1861 :param include_secrets: See :meth:`User.get_api_data`.
1862
1862
1863 """
1863 """
1864 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1864 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1865 # move this methods on models level.
1865 # move this methods on models level.
1866 from rhodecode.model.settings import SettingsModel
1866 from rhodecode.model.settings import SettingsModel
1867 from rhodecode.model.repo import RepoModel
1867 from rhodecode.model.repo import RepoModel
1868
1868
1869 repo = self
1869 repo = self
1870 _user_id, _time, _reason = self.locked
1870 _user_id, _time, _reason = self.locked
1871
1871
1872 data = {
1872 data = {
1873 'repo_id': repo.repo_id,
1873 'repo_id': repo.repo_id,
1874 'repo_name': repo.repo_name,
1874 'repo_name': repo.repo_name,
1875 'repo_type': repo.repo_type,
1875 'repo_type': repo.repo_type,
1876 'clone_uri': repo.clone_uri or '',
1876 'clone_uri': repo.clone_uri or '',
1877 'url': RepoModel().get_url(self),
1877 'url': RepoModel().get_url(self),
1878 'private': repo.private,
1878 'private': repo.private,
1879 'created_on': repo.created_on,
1879 'created_on': repo.created_on,
1880 'description': repo.description_safe,
1880 'description': repo.description_safe,
1881 'landing_rev': repo.landing_rev,
1881 'landing_rev': repo.landing_rev,
1882 'owner': repo.user.username,
1882 'owner': repo.user.username,
1883 'fork_of': repo.fork.repo_name if repo.fork else None,
1883 'fork_of': repo.fork.repo_name if repo.fork else None,
1884 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1884 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1885 'enable_statistics': repo.enable_statistics,
1885 'enable_statistics': repo.enable_statistics,
1886 'enable_locking': repo.enable_locking,
1886 'enable_locking': repo.enable_locking,
1887 'enable_downloads': repo.enable_downloads,
1887 'enable_downloads': repo.enable_downloads,
1888 'last_changeset': repo.changeset_cache,
1888 'last_changeset': repo.changeset_cache,
1889 'locked_by': User.get(_user_id).get_api_data(
1889 'locked_by': User.get(_user_id).get_api_data(
1890 include_secrets=include_secrets) if _user_id else None,
1890 include_secrets=include_secrets) if _user_id else None,
1891 'locked_date': time_to_datetime(_time) if _time else None,
1891 'locked_date': time_to_datetime(_time) if _time else None,
1892 'lock_reason': _reason if _reason else None,
1892 'lock_reason': _reason if _reason else None,
1893 }
1893 }
1894
1894
1895 # TODO: mikhail: should be per-repo settings here
1895 # TODO: mikhail: should be per-repo settings here
1896 rc_config = SettingsModel().get_all_settings()
1896 rc_config = SettingsModel().get_all_settings()
1897 repository_fields = str2bool(
1897 repository_fields = str2bool(
1898 rc_config.get('rhodecode_repository_fields'))
1898 rc_config.get('rhodecode_repository_fields'))
1899 if repository_fields:
1899 if repository_fields:
1900 for f in self.extra_fields:
1900 for f in self.extra_fields:
1901 data[f.field_key_prefixed] = f.field_value
1901 data[f.field_key_prefixed] = f.field_value
1902
1902
1903 return data
1903 return data
1904
1904
1905 @classmethod
1905 @classmethod
1906 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1906 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1907 if not lock_time:
1907 if not lock_time:
1908 lock_time = time.time()
1908 lock_time = time.time()
1909 if not lock_reason:
1909 if not lock_reason:
1910 lock_reason = cls.LOCK_AUTOMATIC
1910 lock_reason = cls.LOCK_AUTOMATIC
1911 repo.locked = [user_id, lock_time, lock_reason]
1911 repo.locked = [user_id, lock_time, lock_reason]
1912 Session().add(repo)
1912 Session().add(repo)
1913 Session().commit()
1913 Session().commit()
1914
1914
1915 @classmethod
1915 @classmethod
1916 def unlock(cls, repo):
1916 def unlock(cls, repo):
1917 repo.locked = None
1917 repo.locked = None
1918 Session().add(repo)
1918 Session().add(repo)
1919 Session().commit()
1919 Session().commit()
1920
1920
1921 @classmethod
1921 @classmethod
1922 def getlock(cls, repo):
1922 def getlock(cls, repo):
1923 return repo.locked
1923 return repo.locked
1924
1924
1925 def is_user_lock(self, user_id):
1925 def is_user_lock(self, user_id):
1926 if self.lock[0]:
1926 if self.lock[0]:
1927 lock_user_id = safe_int(self.lock[0])
1927 lock_user_id = safe_int(self.lock[0])
1928 user_id = safe_int(user_id)
1928 user_id = safe_int(user_id)
1929 # both are ints, and they are equal
1929 # both are ints, and they are equal
1930 return all([lock_user_id, user_id]) and lock_user_id == user_id
1930 return all([lock_user_id, user_id]) and lock_user_id == user_id
1931
1931
1932 return False
1932 return False
1933
1933
1934 def get_locking_state(self, action, user_id, only_when_enabled=True):
1934 def get_locking_state(self, action, user_id, only_when_enabled=True):
1935 """
1935 """
1936 Checks locking on this repository, if locking is enabled and lock is
1936 Checks locking on this repository, if locking is enabled and lock is
1937 present returns a tuple of make_lock, locked, locked_by.
1937 present returns a tuple of make_lock, locked, locked_by.
1938 make_lock can have 3 states None (do nothing) True, make lock
1938 make_lock can have 3 states None (do nothing) True, make lock
1939 False release lock, This value is later propagated to hooks, which
1939 False release lock, This value is later propagated to hooks, which
1940 do the locking. Think about this as signals passed to hooks what to do.
1940 do the locking. Think about this as signals passed to hooks what to do.
1941
1941
1942 """
1942 """
1943 # TODO: johbo: This is part of the business logic and should be moved
1943 # TODO: johbo: This is part of the business logic and should be moved
1944 # into the RepositoryModel.
1944 # into the RepositoryModel.
1945
1945
1946 if action not in ('push', 'pull'):
1946 if action not in ('push', 'pull'):
1947 raise ValueError("Invalid action value: %s" % repr(action))
1947 raise ValueError("Invalid action value: %s" % repr(action))
1948
1948
1949 # defines if locked error should be thrown to user
1949 # defines if locked error should be thrown to user
1950 currently_locked = False
1950 currently_locked = False
1951 # defines if new lock should be made, tri-state
1951 # defines if new lock should be made, tri-state
1952 make_lock = None
1952 make_lock = None
1953 repo = self
1953 repo = self
1954 user = User.get(user_id)
1954 user = User.get(user_id)
1955
1955
1956 lock_info = repo.locked
1956 lock_info = repo.locked
1957
1957
1958 if repo and (repo.enable_locking or not only_when_enabled):
1958 if repo and (repo.enable_locking or not only_when_enabled):
1959 if action == 'push':
1959 if action == 'push':
1960 # check if it's already locked !, if it is compare users
1960 # check if it's already locked !, if it is compare users
1961 locked_by_user_id = lock_info[0]
1961 locked_by_user_id = lock_info[0]
1962 if user.user_id == locked_by_user_id:
1962 if user.user_id == locked_by_user_id:
1963 log.debug(
1963 log.debug(
1964 'Got `push` action from user %s, now unlocking', user)
1964 'Got `push` action from user %s, now unlocking', user)
1965 # unlock if we have push from user who locked
1965 # unlock if we have push from user who locked
1966 make_lock = False
1966 make_lock = False
1967 else:
1967 else:
1968 # we're not the same user who locked, ban with
1968 # we're not the same user who locked, ban with
1969 # code defined in settings (default is 423 HTTP Locked) !
1969 # code defined in settings (default is 423 HTTP Locked) !
1970 log.debug('Repo %s is currently locked by %s', repo, user)
1970 log.debug('Repo %s is currently locked by %s', repo, user)
1971 currently_locked = True
1971 currently_locked = True
1972 elif action == 'pull':
1972 elif action == 'pull':
1973 # [0] user [1] date
1973 # [0] user [1] date
1974 if lock_info[0] and lock_info[1]:
1974 if lock_info[0] and lock_info[1]:
1975 log.debug('Repo %s is currently locked by %s', repo, user)
1975 log.debug('Repo %s is currently locked by %s', repo, user)
1976 currently_locked = True
1976 currently_locked = True
1977 else:
1977 else:
1978 log.debug('Setting lock on repo %s by %s', repo, user)
1978 log.debug('Setting lock on repo %s by %s', repo, user)
1979 make_lock = True
1979 make_lock = True
1980
1980
1981 else:
1981 else:
1982 log.debug('Repository %s do not have locking enabled', repo)
1982 log.debug('Repository %s do not have locking enabled', repo)
1983
1983
1984 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1984 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1985 make_lock, currently_locked, lock_info)
1985 make_lock, currently_locked, lock_info)
1986
1986
1987 from rhodecode.lib.auth import HasRepoPermissionAny
1987 from rhodecode.lib.auth import HasRepoPermissionAny
1988 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1988 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1989 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1989 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1990 # if we don't have at least write permission we cannot make a lock
1990 # if we don't have at least write permission we cannot make a lock
1991 log.debug('lock state reset back to FALSE due to lack '
1991 log.debug('lock state reset back to FALSE due to lack '
1992 'of at least read permission')
1992 'of at least read permission')
1993 make_lock = False
1993 make_lock = False
1994
1994
1995 return make_lock, currently_locked, lock_info
1995 return make_lock, currently_locked, lock_info
1996
1996
1997 @property
1997 @property
1998 def last_db_change(self):
1998 def last_db_change(self):
1999 return self.updated_on
1999 return self.updated_on
2000
2000
2001 @property
2001 @property
2002 def clone_uri_hidden(self):
2002 def clone_uri_hidden(self):
2003 clone_uri = self.clone_uri
2003 clone_uri = self.clone_uri
2004 if clone_uri:
2004 if clone_uri:
2005 import urlobject
2005 import urlobject
2006 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2006 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2007 if url_obj.password:
2007 if url_obj.password:
2008 clone_uri = url_obj.with_password('*****')
2008 clone_uri = url_obj.with_password('*****')
2009 return clone_uri
2009 return clone_uri
2010
2010
2011 def clone_url(self, **override):
2011 def clone_url(self, **override):
2012 from rhodecode.model.settings import SettingsModel
2012 from rhodecode.model.settings import SettingsModel
2013
2013
2014 uri_tmpl = None
2014 uri_tmpl = None
2015 if 'with_id' in override:
2015 if 'with_id' in override:
2016 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2016 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2017 del override['with_id']
2017 del override['with_id']
2018
2018
2019 if 'uri_tmpl' in override:
2019 if 'uri_tmpl' in override:
2020 uri_tmpl = override['uri_tmpl']
2020 uri_tmpl = override['uri_tmpl']
2021 del override['uri_tmpl']
2021 del override['uri_tmpl']
2022
2022
2023 # we didn't override our tmpl from **overrides
2023 # we didn't override our tmpl from **overrides
2024 if not uri_tmpl:
2024 if not uri_tmpl:
2025 rc_config = SettingsModel().get_all_settings(cache=True)
2025 rc_config = SettingsModel().get_all_settings(cache=True)
2026 uri_tmpl = rc_config.get(
2026 uri_tmpl = rc_config.get(
2027 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2027 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2028
2028
2029 request = get_current_request()
2029 request = get_current_request()
2030 return get_clone_url(request=request,
2030 return get_clone_url(request=request,
2031 uri_tmpl=uri_tmpl,
2031 uri_tmpl=uri_tmpl,
2032 repo_name=self.repo_name,
2032 repo_name=self.repo_name,
2033 repo_id=self.repo_id, **override)
2033 repo_id=self.repo_id, **override)
2034
2034
2035 def set_state(self, state):
2035 def set_state(self, state):
2036 self.repo_state = state
2036 self.repo_state = state
2037 Session().add(self)
2037 Session().add(self)
2038 #==========================================================================
2038 #==========================================================================
2039 # SCM PROPERTIES
2039 # SCM PROPERTIES
2040 #==========================================================================
2040 #==========================================================================
2041
2041
2042 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2042 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2043 return get_commit_safe(
2043 return get_commit_safe(
2044 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2044 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2045
2045
2046 def get_changeset(self, rev=None, pre_load=None):
2046 def get_changeset(self, rev=None, pre_load=None):
2047 warnings.warn("Use get_commit", DeprecationWarning)
2047 warnings.warn("Use get_commit", DeprecationWarning)
2048 commit_id = None
2048 commit_id = None
2049 commit_idx = None
2049 commit_idx = None
2050 if isinstance(rev, basestring):
2050 if isinstance(rev, basestring):
2051 commit_id = rev
2051 commit_id = rev
2052 else:
2052 else:
2053 commit_idx = rev
2053 commit_idx = rev
2054 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2054 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2055 pre_load=pre_load)
2055 pre_load=pre_load)
2056
2056
2057 def get_landing_commit(self):
2057 def get_landing_commit(self):
2058 """
2058 """
2059 Returns landing commit, or if that doesn't exist returns the tip
2059 Returns landing commit, or if that doesn't exist returns the tip
2060 """
2060 """
2061 _rev_type, _rev = self.landing_rev
2061 _rev_type, _rev = self.landing_rev
2062 commit = self.get_commit(_rev)
2062 commit = self.get_commit(_rev)
2063 if isinstance(commit, EmptyCommit):
2063 if isinstance(commit, EmptyCommit):
2064 return self.get_commit()
2064 return self.get_commit()
2065 return commit
2065 return commit
2066
2066
2067 def update_commit_cache(self, cs_cache=None, config=None):
2067 def update_commit_cache(self, cs_cache=None, config=None):
2068 """
2068 """
2069 Update cache of last changeset for repository, keys should be::
2069 Update cache of last changeset for repository, keys should be::
2070
2070
2071 short_id
2071 short_id
2072 raw_id
2072 raw_id
2073 revision
2073 revision
2074 parents
2074 parents
2075 message
2075 message
2076 date
2076 date
2077 author
2077 author
2078
2078
2079 :param cs_cache:
2079 :param cs_cache:
2080 """
2080 """
2081 from rhodecode.lib.vcs.backends.base import BaseChangeset
2081 from rhodecode.lib.vcs.backends.base import BaseChangeset
2082 if cs_cache is None:
2082 if cs_cache is None:
2083 # use no-cache version here
2083 # use no-cache version here
2084 scm_repo = self.scm_instance(cache=False, config=config)
2084 scm_repo = self.scm_instance(cache=False, config=config)
2085 if scm_repo:
2085 if scm_repo:
2086 cs_cache = scm_repo.get_commit(
2086 cs_cache = scm_repo.get_commit(
2087 pre_load=["author", "date", "message", "parents"])
2087 pre_load=["author", "date", "message", "parents"])
2088 else:
2088 else:
2089 cs_cache = EmptyCommit()
2089 cs_cache = EmptyCommit()
2090
2090
2091 if isinstance(cs_cache, BaseChangeset):
2091 if isinstance(cs_cache, BaseChangeset):
2092 cs_cache = cs_cache.__json__()
2092 cs_cache = cs_cache.__json__()
2093
2093
2094 def is_outdated(new_cs_cache):
2094 def is_outdated(new_cs_cache):
2095 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2095 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2096 new_cs_cache['revision'] != self.changeset_cache['revision']):
2096 new_cs_cache['revision'] != self.changeset_cache['revision']):
2097 return True
2097 return True
2098 return False
2098 return False
2099
2099
2100 # check if we have maybe already latest cached revision
2100 # check if we have maybe already latest cached revision
2101 if is_outdated(cs_cache) or not self.changeset_cache:
2101 if is_outdated(cs_cache) or not self.changeset_cache:
2102 _default = datetime.datetime.fromtimestamp(0)
2102 _default = datetime.datetime.fromtimestamp(0)
2103 last_change = cs_cache.get('date') or _default
2103 last_change = cs_cache.get('date') or _default
2104 log.debug('updated repo %s with new cs cache %s',
2104 log.debug('updated repo %s with new cs cache %s',
2105 self.repo_name, cs_cache)
2105 self.repo_name, cs_cache)
2106 self.updated_on = last_change
2106 self.updated_on = last_change
2107 self.changeset_cache = cs_cache
2107 self.changeset_cache = cs_cache
2108 Session().add(self)
2108 Session().add(self)
2109 Session().commit()
2109 Session().commit()
2110 else:
2110 else:
2111 log.debug('Skipping update_commit_cache for repo:`%s` '
2111 log.debug('Skipping update_commit_cache for repo:`%s` '
2112 'commit already with latest changes', self.repo_name)
2112 'commit already with latest changes', self.repo_name)
2113
2113
2114 @property
2114 @property
2115 def tip(self):
2115 def tip(self):
2116 return self.get_commit('tip')
2116 return self.get_commit('tip')
2117
2117
2118 @property
2118 @property
2119 def author(self):
2119 def author(self):
2120 return self.tip.author
2120 return self.tip.author
2121
2121
2122 @property
2122 @property
2123 def last_change(self):
2123 def last_change(self):
2124 return self.scm_instance().last_change
2124 return self.scm_instance().last_change
2125
2125
2126 def get_comments(self, revisions=None):
2126 def get_comments(self, revisions=None):
2127 """
2127 """
2128 Returns comments for this repository grouped by revisions
2128 Returns comments for this repository grouped by revisions
2129
2129
2130 :param revisions: filter query by revisions only
2130 :param revisions: filter query by revisions only
2131 """
2131 """
2132 cmts = ChangesetComment.query()\
2132 cmts = ChangesetComment.query()\
2133 .filter(ChangesetComment.repo == self)
2133 .filter(ChangesetComment.repo == self)
2134 if revisions:
2134 if revisions:
2135 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2135 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2136 grouped = collections.defaultdict(list)
2136 grouped = collections.defaultdict(list)
2137 for cmt in cmts.all():
2137 for cmt in cmts.all():
2138 grouped[cmt.revision].append(cmt)
2138 grouped[cmt.revision].append(cmt)
2139 return grouped
2139 return grouped
2140
2140
2141 def statuses(self, revisions=None):
2141 def statuses(self, revisions=None):
2142 """
2142 """
2143 Returns statuses for this repository
2143 Returns statuses for this repository
2144
2144
2145 :param revisions: list of revisions to get statuses for
2145 :param revisions: list of revisions to get statuses for
2146 """
2146 """
2147 statuses = ChangesetStatus.query()\
2147 statuses = ChangesetStatus.query()\
2148 .filter(ChangesetStatus.repo == self)\
2148 .filter(ChangesetStatus.repo == self)\
2149 .filter(ChangesetStatus.version == 0)
2149 .filter(ChangesetStatus.version == 0)
2150
2150
2151 if revisions:
2151 if revisions:
2152 # Try doing the filtering in chunks to avoid hitting limits
2152 # Try doing the filtering in chunks to avoid hitting limits
2153 size = 500
2153 size = 500
2154 status_results = []
2154 status_results = []
2155 for chunk in xrange(0, len(revisions), size):
2155 for chunk in xrange(0, len(revisions), size):
2156 status_results += statuses.filter(
2156 status_results += statuses.filter(
2157 ChangesetStatus.revision.in_(
2157 ChangesetStatus.revision.in_(
2158 revisions[chunk: chunk+size])
2158 revisions[chunk: chunk+size])
2159 ).all()
2159 ).all()
2160 else:
2160 else:
2161 status_results = statuses.all()
2161 status_results = statuses.all()
2162
2162
2163 grouped = {}
2163 grouped = {}
2164
2164
2165 # maybe we have open new pullrequest without a status?
2165 # maybe we have open new pullrequest without a status?
2166 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2166 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2167 status_lbl = ChangesetStatus.get_status_lbl(stat)
2167 status_lbl = ChangesetStatus.get_status_lbl(stat)
2168 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2168 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2169 for rev in pr.revisions:
2169 for rev in pr.revisions:
2170 pr_id = pr.pull_request_id
2170 pr_id = pr.pull_request_id
2171 pr_repo = pr.target_repo.repo_name
2171 pr_repo = pr.target_repo.repo_name
2172 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2172 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2173
2173
2174 for stat in status_results:
2174 for stat in status_results:
2175 pr_id = pr_repo = None
2175 pr_id = pr_repo = None
2176 if stat.pull_request:
2176 if stat.pull_request:
2177 pr_id = stat.pull_request.pull_request_id
2177 pr_id = stat.pull_request.pull_request_id
2178 pr_repo = stat.pull_request.target_repo.repo_name
2178 pr_repo = stat.pull_request.target_repo.repo_name
2179 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2179 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2180 pr_id, pr_repo]
2180 pr_id, pr_repo]
2181 return grouped
2181 return grouped
2182
2182
2183 # ==========================================================================
2183 # ==========================================================================
2184 # SCM CACHE INSTANCE
2184 # SCM CACHE INSTANCE
2185 # ==========================================================================
2185 # ==========================================================================
2186
2186
2187 def scm_instance(self, **kwargs):
2187 def scm_instance(self, **kwargs):
2188 import rhodecode
2188 import rhodecode
2189
2189
2190 # Passing a config will not hit the cache currently only used
2190 # Passing a config will not hit the cache currently only used
2191 # for repo2dbmapper
2191 # for repo2dbmapper
2192 config = kwargs.pop('config', None)
2192 config = kwargs.pop('config', None)
2193 cache = kwargs.pop('cache', None)
2193 cache = kwargs.pop('cache', None)
2194 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2194 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2195 # if cache is NOT defined use default global, else we have a full
2195 # if cache is NOT defined use default global, else we have a full
2196 # control over cache behaviour
2196 # control over cache behaviour
2197 if cache is None and full_cache and not config:
2197 if cache is None and full_cache and not config:
2198 return self._get_instance_cached()
2198 return self._get_instance_cached()
2199 return self._get_instance(cache=bool(cache), config=config)
2199 return self._get_instance(cache=bool(cache), config=config)
2200
2200
2201 def _get_instance_cached(self):
2201 def _get_instance_cached(self):
2202 self._get_instance()
2202 self._get_instance()
2203
2203
2204 def _get_instance(self, cache=True, config=None):
2204 def _get_instance(self, cache=True, config=None):
2205 config = config or self._config
2205 config = config or self._config
2206 custom_wire = {
2206 custom_wire = {
2207 'cache': cache # controls the vcs.remote cache
2207 'cache': cache # controls the vcs.remote cache
2208 }
2208 }
2209 repo = get_vcs_instance(
2209 repo = get_vcs_instance(
2210 repo_path=safe_str(self.repo_full_path),
2210 repo_path=safe_str(self.repo_full_path),
2211 config=config,
2211 config=config,
2212 with_wire=custom_wire,
2212 with_wire=custom_wire,
2213 create=False,
2213 create=False,
2214 _vcs_alias=self.repo_type)
2214 _vcs_alias=self.repo_type)
2215
2215
2216 return repo
2216 return repo
2217
2217
2218 def __json__(self):
2218 def __json__(self):
2219 return {'landing_rev': self.landing_rev}
2219 return {'landing_rev': self.landing_rev}
2220
2220
2221 def get_dict(self):
2221 def get_dict(self):
2222
2222
2223 # Since we transformed `repo_name` to a hybrid property, we need to
2223 # Since we transformed `repo_name` to a hybrid property, we need to
2224 # keep compatibility with the code which uses `repo_name` field.
2224 # keep compatibility with the code which uses `repo_name` field.
2225
2225
2226 result = super(Repository, self).get_dict()
2226 result = super(Repository, self).get_dict()
2227 result['repo_name'] = result.pop('_repo_name', None)
2227 result['repo_name'] = result.pop('_repo_name', None)
2228 return result
2228 return result
2229
2229
2230
2230
2231 class RepoGroup(Base, BaseModel):
2231 class RepoGroup(Base, BaseModel):
2232 __tablename__ = 'groups'
2232 __tablename__ = 'groups'
2233 __table_args__ = (
2233 __table_args__ = (
2234 UniqueConstraint('group_name', 'group_parent_id'),
2234 UniqueConstraint('group_name', 'group_parent_id'),
2235 CheckConstraint('group_id != group_parent_id'),
2235 CheckConstraint('group_id != group_parent_id'),
2236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2237 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2237 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2238 )
2238 )
2239 __mapper_args__ = {'order_by': 'group_name'}
2239 __mapper_args__ = {'order_by': 'group_name'}
2240
2240
2241 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2241 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2242
2242
2243 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2243 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2244 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2244 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2245 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2245 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2246 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2246 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2247 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2247 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2249 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2249 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2250 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2250 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2251 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2251 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2252
2252
2253 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2253 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2254 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2254 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2255 parent_group = relationship('RepoGroup', remote_side=group_id)
2255 parent_group = relationship('RepoGroup', remote_side=group_id)
2256 user = relationship('User')
2256 user = relationship('User')
2257 integrations = relationship('Integration',
2257 integrations = relationship('Integration',
2258 cascade="all, delete, delete-orphan")
2258 cascade="all, delete, delete-orphan")
2259
2259
2260 def __init__(self, group_name='', parent_group=None):
2260 def __init__(self, group_name='', parent_group=None):
2261 self.group_name = group_name
2261 self.group_name = group_name
2262 self.parent_group = parent_group
2262 self.parent_group = parent_group
2263
2263
2264 def __unicode__(self):
2264 def __unicode__(self):
2265 return u"<%s('id:%s:%s')>" % (
2265 return u"<%s('id:%s:%s')>" % (
2266 self.__class__.__name__, self.group_id, self.group_name)
2266 self.__class__.__name__, self.group_id, self.group_name)
2267
2267
2268 @hybrid_property
2268 @hybrid_property
2269 def description_safe(self):
2269 def description_safe(self):
2270 from rhodecode.lib import helpers as h
2270 from rhodecode.lib import helpers as h
2271 return h.escape(self.group_description)
2271 return h.escape(self.group_description)
2272
2272
2273 @classmethod
2273 @classmethod
2274 def _generate_choice(cls, repo_group):
2274 def _generate_choice(cls, repo_group):
2275 from webhelpers.html import literal as _literal
2275 from webhelpers.html import literal as _literal
2276 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2276 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2277 return repo_group.group_id, _name(repo_group.full_path_splitted)
2277 return repo_group.group_id, _name(repo_group.full_path_splitted)
2278
2278
2279 @classmethod
2279 @classmethod
2280 def groups_choices(cls, groups=None, show_empty_group=True):
2280 def groups_choices(cls, groups=None, show_empty_group=True):
2281 if not groups:
2281 if not groups:
2282 groups = cls.query().all()
2282 groups = cls.query().all()
2283
2283
2284 repo_groups = []
2284 repo_groups = []
2285 if show_empty_group:
2285 if show_empty_group:
2286 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2286 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2287
2287
2288 repo_groups.extend([cls._generate_choice(x) for x in groups])
2288 repo_groups.extend([cls._generate_choice(x) for x in groups])
2289
2289
2290 repo_groups = sorted(
2290 repo_groups = sorted(
2291 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2291 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2292 return repo_groups
2292 return repo_groups
2293
2293
2294 @classmethod
2294 @classmethod
2295 def url_sep(cls):
2295 def url_sep(cls):
2296 return URL_SEP
2296 return URL_SEP
2297
2297
2298 @classmethod
2298 @classmethod
2299 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2299 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2300 if case_insensitive:
2300 if case_insensitive:
2301 gr = cls.query().filter(func.lower(cls.group_name)
2301 gr = cls.query().filter(func.lower(cls.group_name)
2302 == func.lower(group_name))
2302 == func.lower(group_name))
2303 else:
2303 else:
2304 gr = cls.query().filter(cls.group_name == group_name)
2304 gr = cls.query().filter(cls.group_name == group_name)
2305 if cache:
2305 if cache:
2306 name_key = _hash_key(group_name)
2306 name_key = _hash_key(group_name)
2307 gr = gr.options(
2307 gr = gr.options(
2308 FromCache("sql_cache_short", "get_group_%s" % name_key))
2308 FromCache("sql_cache_short", "get_group_%s" % name_key))
2309 return gr.scalar()
2309 return gr.scalar()
2310
2310
2311 @classmethod
2311 @classmethod
2312 def get_user_personal_repo_group(cls, user_id):
2312 def get_user_personal_repo_group(cls, user_id):
2313 user = User.get(user_id)
2313 user = User.get(user_id)
2314 if user.username == User.DEFAULT_USER:
2314 if user.username == User.DEFAULT_USER:
2315 return None
2315 return None
2316
2316
2317 return cls.query()\
2317 return cls.query()\
2318 .filter(cls.personal == true()) \
2318 .filter(cls.personal == true()) \
2319 .filter(cls.user == user).scalar()
2319 .filter(cls.user == user).scalar()
2320
2320
2321 @classmethod
2321 @classmethod
2322 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2322 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2323 case_insensitive=True):
2323 case_insensitive=True):
2324 q = RepoGroup.query()
2324 q = RepoGroup.query()
2325
2325
2326 if not isinstance(user_id, Optional):
2326 if not isinstance(user_id, Optional):
2327 q = q.filter(RepoGroup.user_id == user_id)
2327 q = q.filter(RepoGroup.user_id == user_id)
2328
2328
2329 if not isinstance(group_id, Optional):
2329 if not isinstance(group_id, Optional):
2330 q = q.filter(RepoGroup.group_parent_id == group_id)
2330 q = q.filter(RepoGroup.group_parent_id == group_id)
2331
2331
2332 if case_insensitive:
2332 if case_insensitive:
2333 q = q.order_by(func.lower(RepoGroup.group_name))
2333 q = q.order_by(func.lower(RepoGroup.group_name))
2334 else:
2334 else:
2335 q = q.order_by(RepoGroup.group_name)
2335 q = q.order_by(RepoGroup.group_name)
2336 return q.all()
2336 return q.all()
2337
2337
2338 @property
2338 @property
2339 def parents(self):
2339 def parents(self):
2340 parents_recursion_limit = 10
2340 parents_recursion_limit = 10
2341 groups = []
2341 groups = []
2342 if self.parent_group is None:
2342 if self.parent_group is None:
2343 return groups
2343 return groups
2344 cur_gr = self.parent_group
2344 cur_gr = self.parent_group
2345 groups.insert(0, cur_gr)
2345 groups.insert(0, cur_gr)
2346 cnt = 0
2346 cnt = 0
2347 while 1:
2347 while 1:
2348 cnt += 1
2348 cnt += 1
2349 gr = getattr(cur_gr, 'parent_group', None)
2349 gr = getattr(cur_gr, 'parent_group', None)
2350 cur_gr = cur_gr.parent_group
2350 cur_gr = cur_gr.parent_group
2351 if gr is None:
2351 if gr is None:
2352 break
2352 break
2353 if cnt == parents_recursion_limit:
2353 if cnt == parents_recursion_limit:
2354 # this will prevent accidental infinit loops
2354 # this will prevent accidental infinit loops
2355 log.error('more than %s parents found for group %s, stopping '
2355 log.error('more than %s parents found for group %s, stopping '
2356 'recursive parent fetching', parents_recursion_limit, self)
2356 'recursive parent fetching', parents_recursion_limit, self)
2357 break
2357 break
2358
2358
2359 groups.insert(0, gr)
2359 groups.insert(0, gr)
2360 return groups
2360 return groups
2361
2361
2362 @property
2362 @property
2363 def last_db_change(self):
2363 def last_db_change(self):
2364 return self.updated_on
2364 return self.updated_on
2365
2365
2366 @property
2366 @property
2367 def children(self):
2367 def children(self):
2368 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2368 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2369
2369
2370 @property
2370 @property
2371 def name(self):
2371 def name(self):
2372 return self.group_name.split(RepoGroup.url_sep())[-1]
2372 return self.group_name.split(RepoGroup.url_sep())[-1]
2373
2373
2374 @property
2374 @property
2375 def full_path(self):
2375 def full_path(self):
2376 return self.group_name
2376 return self.group_name
2377
2377
2378 @property
2378 @property
2379 def full_path_splitted(self):
2379 def full_path_splitted(self):
2380 return self.group_name.split(RepoGroup.url_sep())
2380 return self.group_name.split(RepoGroup.url_sep())
2381
2381
2382 @property
2382 @property
2383 def repositories(self):
2383 def repositories(self):
2384 return Repository.query()\
2384 return Repository.query()\
2385 .filter(Repository.group == self)\
2385 .filter(Repository.group == self)\
2386 .order_by(Repository.repo_name)
2386 .order_by(Repository.repo_name)
2387
2387
2388 @property
2388 @property
2389 def repositories_recursive_count(self):
2389 def repositories_recursive_count(self):
2390 cnt = self.repositories.count()
2390 cnt = self.repositories.count()
2391
2391
2392 def children_count(group):
2392 def children_count(group):
2393 cnt = 0
2393 cnt = 0
2394 for child in group.children:
2394 for child in group.children:
2395 cnt += child.repositories.count()
2395 cnt += child.repositories.count()
2396 cnt += children_count(child)
2396 cnt += children_count(child)
2397 return cnt
2397 return cnt
2398
2398
2399 return cnt + children_count(self)
2399 return cnt + children_count(self)
2400
2400
2401 def _recursive_objects(self, include_repos=True):
2401 def _recursive_objects(self, include_repos=True):
2402 all_ = []
2402 all_ = []
2403
2403
2404 def _get_members(root_gr):
2404 def _get_members(root_gr):
2405 if include_repos:
2405 if include_repos:
2406 for r in root_gr.repositories:
2406 for r in root_gr.repositories:
2407 all_.append(r)
2407 all_.append(r)
2408 childs = root_gr.children.all()
2408 childs = root_gr.children.all()
2409 if childs:
2409 if childs:
2410 for gr in childs:
2410 for gr in childs:
2411 all_.append(gr)
2411 all_.append(gr)
2412 _get_members(gr)
2412 _get_members(gr)
2413
2413
2414 _get_members(self)
2414 _get_members(self)
2415 return [self] + all_
2415 return [self] + all_
2416
2416
2417 def recursive_groups_and_repos(self):
2417 def recursive_groups_and_repos(self):
2418 """
2418 """
2419 Recursive return all groups, with repositories in those groups
2419 Recursive return all groups, with repositories in those groups
2420 """
2420 """
2421 return self._recursive_objects()
2421 return self._recursive_objects()
2422
2422
2423 def recursive_groups(self):
2423 def recursive_groups(self):
2424 """
2424 """
2425 Returns all children groups for this group including children of children
2425 Returns all children groups for this group including children of children
2426 """
2426 """
2427 return self._recursive_objects(include_repos=False)
2427 return self._recursive_objects(include_repos=False)
2428
2428
2429 def get_new_name(self, group_name):
2429 def get_new_name(self, group_name):
2430 """
2430 """
2431 returns new full group name based on parent and new name
2431 returns new full group name based on parent and new name
2432
2432
2433 :param group_name:
2433 :param group_name:
2434 """
2434 """
2435 path_prefix = (self.parent_group.full_path_splitted if
2435 path_prefix = (self.parent_group.full_path_splitted if
2436 self.parent_group else [])
2436 self.parent_group else [])
2437 return RepoGroup.url_sep().join(path_prefix + [group_name])
2437 return RepoGroup.url_sep().join(path_prefix + [group_name])
2438
2438
2439 def permissions(self, with_admins=True, with_owner=True):
2439 def permissions(self, with_admins=True, with_owner=True):
2440 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2440 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2441 q = q.options(joinedload(UserRepoGroupToPerm.group),
2441 q = q.options(joinedload(UserRepoGroupToPerm.group),
2442 joinedload(UserRepoGroupToPerm.user),
2442 joinedload(UserRepoGroupToPerm.user),
2443 joinedload(UserRepoGroupToPerm.permission),)
2443 joinedload(UserRepoGroupToPerm.permission),)
2444
2444
2445 # get owners and admins and permissions. We do a trick of re-writing
2445 # get owners and admins and permissions. We do a trick of re-writing
2446 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2446 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2447 # has a global reference and changing one object propagates to all
2447 # has a global reference and changing one object propagates to all
2448 # others. This means if admin is also an owner admin_row that change
2448 # others. This means if admin is also an owner admin_row that change
2449 # would propagate to both objects
2449 # would propagate to both objects
2450 perm_rows = []
2450 perm_rows = []
2451 for _usr in q.all():
2451 for _usr in q.all():
2452 usr = AttributeDict(_usr.user.get_dict())
2452 usr = AttributeDict(_usr.user.get_dict())
2453 usr.permission = _usr.permission.permission_name
2453 usr.permission = _usr.permission.permission_name
2454 perm_rows.append(usr)
2454 perm_rows.append(usr)
2455
2455
2456 # filter the perm rows by 'default' first and then sort them by
2456 # filter the perm rows by 'default' first and then sort them by
2457 # admin,write,read,none permissions sorted again alphabetically in
2457 # admin,write,read,none permissions sorted again alphabetically in
2458 # each group
2458 # each group
2459 perm_rows = sorted(perm_rows, key=display_sort)
2459 perm_rows = sorted(perm_rows, key=display_sort)
2460
2460
2461 _admin_perm = 'group.admin'
2461 _admin_perm = 'group.admin'
2462 owner_row = []
2462 owner_row = []
2463 if with_owner:
2463 if with_owner:
2464 usr = AttributeDict(self.user.get_dict())
2464 usr = AttributeDict(self.user.get_dict())
2465 usr.owner_row = True
2465 usr.owner_row = True
2466 usr.permission = _admin_perm
2466 usr.permission = _admin_perm
2467 owner_row.append(usr)
2467 owner_row.append(usr)
2468
2468
2469 super_admin_rows = []
2469 super_admin_rows = []
2470 if with_admins:
2470 if with_admins:
2471 for usr in User.get_all_super_admins():
2471 for usr in User.get_all_super_admins():
2472 # if this admin is also owner, don't double the record
2472 # if this admin is also owner, don't double the record
2473 if usr.user_id == owner_row[0].user_id:
2473 if usr.user_id == owner_row[0].user_id:
2474 owner_row[0].admin_row = True
2474 owner_row[0].admin_row = True
2475 else:
2475 else:
2476 usr = AttributeDict(usr.get_dict())
2476 usr = AttributeDict(usr.get_dict())
2477 usr.admin_row = True
2477 usr.admin_row = True
2478 usr.permission = _admin_perm
2478 usr.permission = _admin_perm
2479 super_admin_rows.append(usr)
2479 super_admin_rows.append(usr)
2480
2480
2481 return super_admin_rows + owner_row + perm_rows
2481 return super_admin_rows + owner_row + perm_rows
2482
2482
2483 def permission_user_groups(self):
2483 def permission_user_groups(self):
2484 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2484 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2485 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2485 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2486 joinedload(UserGroupRepoGroupToPerm.users_group),
2486 joinedload(UserGroupRepoGroupToPerm.users_group),
2487 joinedload(UserGroupRepoGroupToPerm.permission),)
2487 joinedload(UserGroupRepoGroupToPerm.permission),)
2488
2488
2489 perm_rows = []
2489 perm_rows = []
2490 for _user_group in q.all():
2490 for _user_group in q.all():
2491 usr = AttributeDict(_user_group.users_group.get_dict())
2491 usr = AttributeDict(_user_group.users_group.get_dict())
2492 usr.permission = _user_group.permission.permission_name
2492 usr.permission = _user_group.permission.permission_name
2493 perm_rows.append(usr)
2493 perm_rows.append(usr)
2494
2494
2495 return perm_rows
2495 return perm_rows
2496
2496
2497 def get_api_data(self):
2497 def get_api_data(self):
2498 """
2498 """
2499 Common function for generating api data
2499 Common function for generating api data
2500
2500
2501 """
2501 """
2502 group = self
2502 group = self
2503 data = {
2503 data = {
2504 'group_id': group.group_id,
2504 'group_id': group.group_id,
2505 'group_name': group.group_name,
2505 'group_name': group.group_name,
2506 'group_description': group.description_safe,
2506 'group_description': group.description_safe,
2507 'parent_group': group.parent_group.group_name if group.parent_group else None,
2507 'parent_group': group.parent_group.group_name if group.parent_group else None,
2508 'repositories': [x.repo_name for x in group.repositories],
2508 'repositories': [x.repo_name for x in group.repositories],
2509 'owner': group.user.username,
2509 'owner': group.user.username,
2510 }
2510 }
2511 return data
2511 return data
2512
2512
2513
2513
2514 class Permission(Base, BaseModel):
2514 class Permission(Base, BaseModel):
2515 __tablename__ = 'permissions'
2515 __tablename__ = 'permissions'
2516 __table_args__ = (
2516 __table_args__ = (
2517 Index('p_perm_name_idx', 'permission_name'),
2517 Index('p_perm_name_idx', 'permission_name'),
2518 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2518 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2519 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2519 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2520 )
2520 )
2521 PERMS = [
2521 PERMS = [
2522 ('hg.admin', _('RhodeCode Super Administrator')),
2522 ('hg.admin', _('RhodeCode Super Administrator')),
2523
2523
2524 ('repository.none', _('Repository no access')),
2524 ('repository.none', _('Repository no access')),
2525 ('repository.read', _('Repository read access')),
2525 ('repository.read', _('Repository read access')),
2526 ('repository.write', _('Repository write access')),
2526 ('repository.write', _('Repository write access')),
2527 ('repository.admin', _('Repository admin access')),
2527 ('repository.admin', _('Repository admin access')),
2528
2528
2529 ('group.none', _('Repository group no access')),
2529 ('group.none', _('Repository group no access')),
2530 ('group.read', _('Repository group read access')),
2530 ('group.read', _('Repository group read access')),
2531 ('group.write', _('Repository group write access')),
2531 ('group.write', _('Repository group write access')),
2532 ('group.admin', _('Repository group admin access')),
2532 ('group.admin', _('Repository group admin access')),
2533
2533
2534 ('usergroup.none', _('User group no access')),
2534 ('usergroup.none', _('User group no access')),
2535 ('usergroup.read', _('User group read access')),
2535 ('usergroup.read', _('User group read access')),
2536 ('usergroup.write', _('User group write access')),
2536 ('usergroup.write', _('User group write access')),
2537 ('usergroup.admin', _('User group admin access')),
2537 ('usergroup.admin', _('User group admin access')),
2538
2538
2539 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2539 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2540 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2540 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2541
2541
2542 ('hg.usergroup.create.false', _('User Group creation disabled')),
2542 ('hg.usergroup.create.false', _('User Group creation disabled')),
2543 ('hg.usergroup.create.true', _('User Group creation enabled')),
2543 ('hg.usergroup.create.true', _('User Group creation enabled')),
2544
2544
2545 ('hg.create.none', _('Repository creation disabled')),
2545 ('hg.create.none', _('Repository creation disabled')),
2546 ('hg.create.repository', _('Repository creation enabled')),
2546 ('hg.create.repository', _('Repository creation enabled')),
2547 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2547 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2548 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2548 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2549
2549
2550 ('hg.fork.none', _('Repository forking disabled')),
2550 ('hg.fork.none', _('Repository forking disabled')),
2551 ('hg.fork.repository', _('Repository forking enabled')),
2551 ('hg.fork.repository', _('Repository forking enabled')),
2552
2552
2553 ('hg.register.none', _('Registration disabled')),
2553 ('hg.register.none', _('Registration disabled')),
2554 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2554 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2555 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2555 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2556
2556
2557 ('hg.password_reset.enabled', _('Password reset enabled')),
2557 ('hg.password_reset.enabled', _('Password reset enabled')),
2558 ('hg.password_reset.hidden', _('Password reset hidden')),
2558 ('hg.password_reset.hidden', _('Password reset hidden')),
2559 ('hg.password_reset.disabled', _('Password reset disabled')),
2559 ('hg.password_reset.disabled', _('Password reset disabled')),
2560
2560
2561 ('hg.extern_activate.manual', _('Manual activation of external account')),
2561 ('hg.extern_activate.manual', _('Manual activation of external account')),
2562 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2562 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2563
2563
2564 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2564 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2565 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2565 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2566 ]
2566 ]
2567
2567
2568 # definition of system default permissions for DEFAULT user
2568 # definition of system default permissions for DEFAULT user
2569 DEFAULT_USER_PERMISSIONS = [
2569 DEFAULT_USER_PERMISSIONS = [
2570 'repository.read',
2570 'repository.read',
2571 'group.read',
2571 'group.read',
2572 'usergroup.read',
2572 'usergroup.read',
2573 'hg.create.repository',
2573 'hg.create.repository',
2574 'hg.repogroup.create.false',
2574 'hg.repogroup.create.false',
2575 'hg.usergroup.create.false',
2575 'hg.usergroup.create.false',
2576 'hg.create.write_on_repogroup.true',
2576 'hg.create.write_on_repogroup.true',
2577 'hg.fork.repository',
2577 'hg.fork.repository',
2578 'hg.register.manual_activate',
2578 'hg.register.manual_activate',
2579 'hg.password_reset.enabled',
2579 'hg.password_reset.enabled',
2580 'hg.extern_activate.auto',
2580 'hg.extern_activate.auto',
2581 'hg.inherit_default_perms.true',
2581 'hg.inherit_default_perms.true',
2582 ]
2582 ]
2583
2583
2584 # defines which permissions are more important higher the more important
2584 # defines which permissions are more important higher the more important
2585 # Weight defines which permissions are more important.
2585 # Weight defines which permissions are more important.
2586 # The higher number the more important.
2586 # The higher number the more important.
2587 PERM_WEIGHTS = {
2587 PERM_WEIGHTS = {
2588 'repository.none': 0,
2588 'repository.none': 0,
2589 'repository.read': 1,
2589 'repository.read': 1,
2590 'repository.write': 3,
2590 'repository.write': 3,
2591 'repository.admin': 4,
2591 'repository.admin': 4,
2592
2592
2593 'group.none': 0,
2593 'group.none': 0,
2594 'group.read': 1,
2594 'group.read': 1,
2595 'group.write': 3,
2595 'group.write': 3,
2596 'group.admin': 4,
2596 'group.admin': 4,
2597
2597
2598 'usergroup.none': 0,
2598 'usergroup.none': 0,
2599 'usergroup.read': 1,
2599 'usergroup.read': 1,
2600 'usergroup.write': 3,
2600 'usergroup.write': 3,
2601 'usergroup.admin': 4,
2601 'usergroup.admin': 4,
2602
2602
2603 'hg.repogroup.create.false': 0,
2603 'hg.repogroup.create.false': 0,
2604 'hg.repogroup.create.true': 1,
2604 'hg.repogroup.create.true': 1,
2605
2605
2606 'hg.usergroup.create.false': 0,
2606 'hg.usergroup.create.false': 0,
2607 'hg.usergroup.create.true': 1,
2607 'hg.usergroup.create.true': 1,
2608
2608
2609 'hg.fork.none': 0,
2609 'hg.fork.none': 0,
2610 'hg.fork.repository': 1,
2610 'hg.fork.repository': 1,
2611 'hg.create.none': 0,
2611 'hg.create.none': 0,
2612 'hg.create.repository': 1
2612 'hg.create.repository': 1
2613 }
2613 }
2614
2614
2615 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2615 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2616 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2616 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2617 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2617 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2618
2618
2619 def __unicode__(self):
2619 def __unicode__(self):
2620 return u"<%s('%s:%s')>" % (
2620 return u"<%s('%s:%s')>" % (
2621 self.__class__.__name__, self.permission_id, self.permission_name
2621 self.__class__.__name__, self.permission_id, self.permission_name
2622 )
2622 )
2623
2623
2624 @classmethod
2624 @classmethod
2625 def get_by_key(cls, key):
2625 def get_by_key(cls, key):
2626 return cls.query().filter(cls.permission_name == key).scalar()
2626 return cls.query().filter(cls.permission_name == key).scalar()
2627
2627
2628 @classmethod
2628 @classmethod
2629 def get_default_repo_perms(cls, user_id, repo_id=None):
2629 def get_default_repo_perms(cls, user_id, repo_id=None):
2630 q = Session().query(UserRepoToPerm, Repository, Permission)\
2630 q = Session().query(UserRepoToPerm, Repository, Permission)\
2631 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2631 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2632 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2632 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2633 .filter(UserRepoToPerm.user_id == user_id)
2633 .filter(UserRepoToPerm.user_id == user_id)
2634 if repo_id:
2634 if repo_id:
2635 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2635 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2636 return q.all()
2636 return q.all()
2637
2637
2638 @classmethod
2638 @classmethod
2639 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2639 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2640 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2640 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2641 .join(
2641 .join(
2642 Permission,
2642 Permission,
2643 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2643 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2644 .join(
2644 .join(
2645 Repository,
2645 Repository,
2646 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2646 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2647 .join(
2647 .join(
2648 UserGroup,
2648 UserGroup,
2649 UserGroupRepoToPerm.users_group_id ==
2649 UserGroupRepoToPerm.users_group_id ==
2650 UserGroup.users_group_id)\
2650 UserGroup.users_group_id)\
2651 .join(
2651 .join(
2652 UserGroupMember,
2652 UserGroupMember,
2653 UserGroupRepoToPerm.users_group_id ==
2653 UserGroupRepoToPerm.users_group_id ==
2654 UserGroupMember.users_group_id)\
2654 UserGroupMember.users_group_id)\
2655 .filter(
2655 .filter(
2656 UserGroupMember.user_id == user_id,
2656 UserGroupMember.user_id == user_id,
2657 UserGroup.users_group_active == true())
2657 UserGroup.users_group_active == true())
2658 if repo_id:
2658 if repo_id:
2659 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2659 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2660 return q.all()
2660 return q.all()
2661
2661
2662 @classmethod
2662 @classmethod
2663 def get_default_group_perms(cls, user_id, repo_group_id=None):
2663 def get_default_group_perms(cls, user_id, repo_group_id=None):
2664 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2664 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2665 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2665 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2666 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2666 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2667 .filter(UserRepoGroupToPerm.user_id == user_id)
2667 .filter(UserRepoGroupToPerm.user_id == user_id)
2668 if repo_group_id:
2668 if repo_group_id:
2669 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2669 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2670 return q.all()
2670 return q.all()
2671
2671
2672 @classmethod
2672 @classmethod
2673 def get_default_group_perms_from_user_group(
2673 def get_default_group_perms_from_user_group(
2674 cls, user_id, repo_group_id=None):
2674 cls, user_id, repo_group_id=None):
2675 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2675 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2676 .join(
2676 .join(
2677 Permission,
2677 Permission,
2678 UserGroupRepoGroupToPerm.permission_id ==
2678 UserGroupRepoGroupToPerm.permission_id ==
2679 Permission.permission_id)\
2679 Permission.permission_id)\
2680 .join(
2680 .join(
2681 RepoGroup,
2681 RepoGroup,
2682 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2682 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2683 .join(
2683 .join(
2684 UserGroup,
2684 UserGroup,
2685 UserGroupRepoGroupToPerm.users_group_id ==
2685 UserGroupRepoGroupToPerm.users_group_id ==
2686 UserGroup.users_group_id)\
2686 UserGroup.users_group_id)\
2687 .join(
2687 .join(
2688 UserGroupMember,
2688 UserGroupMember,
2689 UserGroupRepoGroupToPerm.users_group_id ==
2689 UserGroupRepoGroupToPerm.users_group_id ==
2690 UserGroupMember.users_group_id)\
2690 UserGroupMember.users_group_id)\
2691 .filter(
2691 .filter(
2692 UserGroupMember.user_id == user_id,
2692 UserGroupMember.user_id == user_id,
2693 UserGroup.users_group_active == true())
2693 UserGroup.users_group_active == true())
2694 if repo_group_id:
2694 if repo_group_id:
2695 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2695 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2696 return q.all()
2696 return q.all()
2697
2697
2698 @classmethod
2698 @classmethod
2699 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2699 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2700 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2700 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2701 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2701 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2702 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2702 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2703 .filter(UserUserGroupToPerm.user_id == user_id)
2703 .filter(UserUserGroupToPerm.user_id == user_id)
2704 if user_group_id:
2704 if user_group_id:
2705 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2705 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2706 return q.all()
2706 return q.all()
2707
2707
2708 @classmethod
2708 @classmethod
2709 def get_default_user_group_perms_from_user_group(
2709 def get_default_user_group_perms_from_user_group(
2710 cls, user_id, user_group_id=None):
2710 cls, user_id, user_group_id=None):
2711 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2711 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2712 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2712 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2713 .join(
2713 .join(
2714 Permission,
2714 Permission,
2715 UserGroupUserGroupToPerm.permission_id ==
2715 UserGroupUserGroupToPerm.permission_id ==
2716 Permission.permission_id)\
2716 Permission.permission_id)\
2717 .join(
2717 .join(
2718 TargetUserGroup,
2718 TargetUserGroup,
2719 UserGroupUserGroupToPerm.target_user_group_id ==
2719 UserGroupUserGroupToPerm.target_user_group_id ==
2720 TargetUserGroup.users_group_id)\
2720 TargetUserGroup.users_group_id)\
2721 .join(
2721 .join(
2722 UserGroup,
2722 UserGroup,
2723 UserGroupUserGroupToPerm.user_group_id ==
2723 UserGroupUserGroupToPerm.user_group_id ==
2724 UserGroup.users_group_id)\
2724 UserGroup.users_group_id)\
2725 .join(
2725 .join(
2726 UserGroupMember,
2726 UserGroupMember,
2727 UserGroupUserGroupToPerm.user_group_id ==
2727 UserGroupUserGroupToPerm.user_group_id ==
2728 UserGroupMember.users_group_id)\
2728 UserGroupMember.users_group_id)\
2729 .filter(
2729 .filter(
2730 UserGroupMember.user_id == user_id,
2730 UserGroupMember.user_id == user_id,
2731 UserGroup.users_group_active == true())
2731 UserGroup.users_group_active == true())
2732 if user_group_id:
2732 if user_group_id:
2733 q = q.filter(
2733 q = q.filter(
2734 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2734 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2735
2735
2736 return q.all()
2736 return q.all()
2737
2737
2738
2738
2739 class UserRepoToPerm(Base, BaseModel):
2739 class UserRepoToPerm(Base, BaseModel):
2740 __tablename__ = 'repo_to_perm'
2740 __tablename__ = 'repo_to_perm'
2741 __table_args__ = (
2741 __table_args__ = (
2742 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2742 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2743 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2743 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2744 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2744 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2745 )
2745 )
2746 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2746 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2747 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2747 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2748 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2748 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2749 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2749 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2750
2750
2751 user = relationship('User')
2751 user = relationship('User')
2752 repository = relationship('Repository')
2752 repository = relationship('Repository')
2753 permission = relationship('Permission')
2753 permission = relationship('Permission')
2754
2754
2755 @classmethod
2755 @classmethod
2756 def create(cls, user, repository, permission):
2756 def create(cls, user, repository, permission):
2757 n = cls()
2757 n = cls()
2758 n.user = user
2758 n.user = user
2759 n.repository = repository
2759 n.repository = repository
2760 n.permission = permission
2760 n.permission = permission
2761 Session().add(n)
2761 Session().add(n)
2762 return n
2762 return n
2763
2763
2764 def __unicode__(self):
2764 def __unicode__(self):
2765 return u'<%s => %s >' % (self.user, self.repository)
2765 return u'<%s => %s >' % (self.user, self.repository)
2766
2766
2767
2767
2768 class UserUserGroupToPerm(Base, BaseModel):
2768 class UserUserGroupToPerm(Base, BaseModel):
2769 __tablename__ = 'user_user_group_to_perm'
2769 __tablename__ = 'user_user_group_to_perm'
2770 __table_args__ = (
2770 __table_args__ = (
2771 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2771 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2774 )
2774 )
2775 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2775 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2776 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2776 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2777 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2777 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2778 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2778 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2779
2779
2780 user = relationship('User')
2780 user = relationship('User')
2781 user_group = relationship('UserGroup')
2781 user_group = relationship('UserGroup')
2782 permission = relationship('Permission')
2782 permission = relationship('Permission')
2783
2783
2784 @classmethod
2784 @classmethod
2785 def create(cls, user, user_group, permission):
2785 def create(cls, user, user_group, permission):
2786 n = cls()
2786 n = cls()
2787 n.user = user
2787 n.user = user
2788 n.user_group = user_group
2788 n.user_group = user_group
2789 n.permission = permission
2789 n.permission = permission
2790 Session().add(n)
2790 Session().add(n)
2791 return n
2791 return n
2792
2792
2793 def __unicode__(self):
2793 def __unicode__(self):
2794 return u'<%s => %s >' % (self.user, self.user_group)
2794 return u'<%s => %s >' % (self.user, self.user_group)
2795
2795
2796
2796
2797 class UserToPerm(Base, BaseModel):
2797 class UserToPerm(Base, BaseModel):
2798 __tablename__ = 'user_to_perm'
2798 __tablename__ = 'user_to_perm'
2799 __table_args__ = (
2799 __table_args__ = (
2800 UniqueConstraint('user_id', 'permission_id'),
2800 UniqueConstraint('user_id', 'permission_id'),
2801 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2801 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2803 )
2803 )
2804 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2804 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2805 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2805 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2806 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2806 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2807
2807
2808 user = relationship('User')
2808 user = relationship('User')
2809 permission = relationship('Permission', lazy='joined')
2809 permission = relationship('Permission', lazy='joined')
2810
2810
2811 def __unicode__(self):
2811 def __unicode__(self):
2812 return u'<%s => %s >' % (self.user, self.permission)
2812 return u'<%s => %s >' % (self.user, self.permission)
2813
2813
2814
2814
2815 class UserGroupRepoToPerm(Base, BaseModel):
2815 class UserGroupRepoToPerm(Base, BaseModel):
2816 __tablename__ = 'users_group_repo_to_perm'
2816 __tablename__ = 'users_group_repo_to_perm'
2817 __table_args__ = (
2817 __table_args__ = (
2818 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2818 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2819 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2819 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2820 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2820 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2821 )
2821 )
2822 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2822 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2823 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2823 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2824 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2824 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2825 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2825 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2826
2826
2827 users_group = relationship('UserGroup')
2827 users_group = relationship('UserGroup')
2828 permission = relationship('Permission')
2828 permission = relationship('Permission')
2829 repository = relationship('Repository')
2829 repository = relationship('Repository')
2830
2830
2831 @classmethod
2831 @classmethod
2832 def create(cls, users_group, repository, permission):
2832 def create(cls, users_group, repository, permission):
2833 n = cls()
2833 n = cls()
2834 n.users_group = users_group
2834 n.users_group = users_group
2835 n.repository = repository
2835 n.repository = repository
2836 n.permission = permission
2836 n.permission = permission
2837 Session().add(n)
2837 Session().add(n)
2838 return n
2838 return n
2839
2839
2840 def __unicode__(self):
2840 def __unicode__(self):
2841 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2841 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2842
2842
2843
2843
2844 class UserGroupUserGroupToPerm(Base, BaseModel):
2844 class UserGroupUserGroupToPerm(Base, BaseModel):
2845 __tablename__ = 'user_group_user_group_to_perm'
2845 __tablename__ = 'user_group_user_group_to_perm'
2846 __table_args__ = (
2846 __table_args__ = (
2847 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2847 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2848 CheckConstraint('target_user_group_id != user_group_id'),
2848 CheckConstraint('target_user_group_id != user_group_id'),
2849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2850 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2850 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2851 )
2851 )
2852 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)
2852 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)
2853 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2853 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2854 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2854 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2855 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2855 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2856
2856
2857 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2857 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2858 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2858 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2859 permission = relationship('Permission')
2859 permission = relationship('Permission')
2860
2860
2861 @classmethod
2861 @classmethod
2862 def create(cls, target_user_group, user_group, permission):
2862 def create(cls, target_user_group, user_group, permission):
2863 n = cls()
2863 n = cls()
2864 n.target_user_group = target_user_group
2864 n.target_user_group = target_user_group
2865 n.user_group = user_group
2865 n.user_group = user_group
2866 n.permission = permission
2866 n.permission = permission
2867 Session().add(n)
2867 Session().add(n)
2868 return n
2868 return n
2869
2869
2870 def __unicode__(self):
2870 def __unicode__(self):
2871 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2871 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2872
2872
2873
2873
2874 class UserGroupToPerm(Base, BaseModel):
2874 class UserGroupToPerm(Base, BaseModel):
2875 __tablename__ = 'users_group_to_perm'
2875 __tablename__ = 'users_group_to_perm'
2876 __table_args__ = (
2876 __table_args__ = (
2877 UniqueConstraint('users_group_id', 'permission_id',),
2877 UniqueConstraint('users_group_id', 'permission_id',),
2878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2879 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2879 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2880 )
2880 )
2881 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2881 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2882 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2882 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2883 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2883 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2884
2884
2885 users_group = relationship('UserGroup')
2885 users_group = relationship('UserGroup')
2886 permission = relationship('Permission')
2886 permission = relationship('Permission')
2887
2887
2888
2888
2889 class UserRepoGroupToPerm(Base, BaseModel):
2889 class UserRepoGroupToPerm(Base, BaseModel):
2890 __tablename__ = 'user_repo_group_to_perm'
2890 __tablename__ = 'user_repo_group_to_perm'
2891 __table_args__ = (
2891 __table_args__ = (
2892 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2892 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2893 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2893 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2894 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2894 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2895 )
2895 )
2896
2896
2897 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2897 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2898 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2898 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2899 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2899 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2900 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2900 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2901
2901
2902 user = relationship('User')
2902 user = relationship('User')
2903 group = relationship('RepoGroup')
2903 group = relationship('RepoGroup')
2904 permission = relationship('Permission')
2904 permission = relationship('Permission')
2905
2905
2906 @classmethod
2906 @classmethod
2907 def create(cls, user, repository_group, permission):
2907 def create(cls, user, repository_group, permission):
2908 n = cls()
2908 n = cls()
2909 n.user = user
2909 n.user = user
2910 n.group = repository_group
2910 n.group = repository_group
2911 n.permission = permission
2911 n.permission = permission
2912 Session().add(n)
2912 Session().add(n)
2913 return n
2913 return n
2914
2914
2915
2915
2916 class UserGroupRepoGroupToPerm(Base, BaseModel):
2916 class UserGroupRepoGroupToPerm(Base, BaseModel):
2917 __tablename__ = 'users_group_repo_group_to_perm'
2917 __tablename__ = 'users_group_repo_group_to_perm'
2918 __table_args__ = (
2918 __table_args__ = (
2919 UniqueConstraint('users_group_id', 'group_id'),
2919 UniqueConstraint('users_group_id', 'group_id'),
2920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2922 )
2922 )
2923
2923
2924 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)
2924 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)
2925 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2925 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2926 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2926 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2927 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2927 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2928
2928
2929 users_group = relationship('UserGroup')
2929 users_group = relationship('UserGroup')
2930 permission = relationship('Permission')
2930 permission = relationship('Permission')
2931 group = relationship('RepoGroup')
2931 group = relationship('RepoGroup')
2932
2932
2933 @classmethod
2933 @classmethod
2934 def create(cls, user_group, repository_group, permission):
2934 def create(cls, user_group, repository_group, permission):
2935 n = cls()
2935 n = cls()
2936 n.users_group = user_group
2936 n.users_group = user_group
2937 n.group = repository_group
2937 n.group = repository_group
2938 n.permission = permission
2938 n.permission = permission
2939 Session().add(n)
2939 Session().add(n)
2940 return n
2940 return n
2941
2941
2942 def __unicode__(self):
2942 def __unicode__(self):
2943 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2943 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2944
2944
2945
2945
2946 class Statistics(Base, BaseModel):
2946 class Statistics(Base, BaseModel):
2947 __tablename__ = 'statistics'
2947 __tablename__ = 'statistics'
2948 __table_args__ = (
2948 __table_args__ = (
2949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2951 )
2951 )
2952 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2952 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2953 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2953 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2954 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2954 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2955 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2955 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2956 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2956 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2957 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2957 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2958
2958
2959 repository = relationship('Repository', single_parent=True)
2959 repository = relationship('Repository', single_parent=True)
2960
2960
2961
2961
2962 class UserFollowing(Base, BaseModel):
2962 class UserFollowing(Base, BaseModel):
2963 __tablename__ = 'user_followings'
2963 __tablename__ = 'user_followings'
2964 __table_args__ = (
2964 __table_args__ = (
2965 UniqueConstraint('user_id', 'follows_repository_id'),
2965 UniqueConstraint('user_id', 'follows_repository_id'),
2966 UniqueConstraint('user_id', 'follows_user_id'),
2966 UniqueConstraint('user_id', 'follows_user_id'),
2967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2968 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2968 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2969 )
2969 )
2970
2970
2971 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2971 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2972 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2972 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2973 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2973 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2974 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2974 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2975 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2975 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2976
2976
2977 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2977 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2978
2978
2979 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2979 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2980 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2980 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2981
2981
2982 @classmethod
2982 @classmethod
2983 def get_repo_followers(cls, repo_id):
2983 def get_repo_followers(cls, repo_id):
2984 return cls.query().filter(cls.follows_repo_id == repo_id)
2984 return cls.query().filter(cls.follows_repo_id == repo_id)
2985
2985
2986
2986
2987 class CacheKey(Base, BaseModel):
2987 class CacheKey(Base, BaseModel):
2988 __tablename__ = 'cache_invalidation'
2988 __tablename__ = 'cache_invalidation'
2989 __table_args__ = (
2989 __table_args__ = (
2990 UniqueConstraint('cache_key'),
2990 UniqueConstraint('cache_key'),
2991 Index('key_idx', 'cache_key'),
2991 Index('key_idx', 'cache_key'),
2992 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2992 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2993 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2993 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2994 )
2994 )
2995 CACHE_TYPE_ATOM = 'ATOM'
2995 CACHE_TYPE_ATOM = 'ATOM'
2996 CACHE_TYPE_RSS = 'RSS'
2996 CACHE_TYPE_RSS = 'RSS'
2997 CACHE_TYPE_README = 'README'
2997 CACHE_TYPE_README = 'README'
2998
2998
2999 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2999 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3000 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3000 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3001 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3001 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3002 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3002 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3003
3003
3004 def __init__(self, cache_key, cache_args=''):
3004 def __init__(self, cache_key, cache_args=''):
3005 self.cache_key = cache_key
3005 self.cache_key = cache_key
3006 self.cache_args = cache_args
3006 self.cache_args = cache_args
3007 self.cache_active = False
3007 self.cache_active = False
3008
3008
3009 def __unicode__(self):
3009 def __unicode__(self):
3010 return u"<%s('%s:%s[%s]')>" % (
3010 return u"<%s('%s:%s[%s]')>" % (
3011 self.__class__.__name__,
3011 self.__class__.__name__,
3012 self.cache_id, self.cache_key, self.cache_active)
3012 self.cache_id, self.cache_key, self.cache_active)
3013
3013
3014 def _cache_key_partition(self):
3014 def _cache_key_partition(self):
3015 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3015 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3016 return prefix, repo_name, suffix
3016 return prefix, repo_name, suffix
3017
3017
3018 def get_prefix(self):
3018 def get_prefix(self):
3019 """
3019 """
3020 Try to extract prefix from existing cache key. The key could consist
3020 Try to extract prefix from existing cache key. The key could consist
3021 of prefix, repo_name, suffix
3021 of prefix, repo_name, suffix
3022 """
3022 """
3023 # this returns prefix, repo_name, suffix
3023 # this returns prefix, repo_name, suffix
3024 return self._cache_key_partition()[0]
3024 return self._cache_key_partition()[0]
3025
3025
3026 def get_suffix(self):
3026 def get_suffix(self):
3027 """
3027 """
3028 get suffix that might have been used in _get_cache_key to
3028 get suffix that might have been used in _get_cache_key to
3029 generate self.cache_key. Only used for informational purposes
3029 generate self.cache_key. Only used for informational purposes
3030 in repo_edit.mako.
3030 in repo_edit.mako.
3031 """
3031 """
3032 # prefix, repo_name, suffix
3032 # prefix, repo_name, suffix
3033 return self._cache_key_partition()[2]
3033 return self._cache_key_partition()[2]
3034
3034
3035 @classmethod
3035 @classmethod
3036 def delete_all_cache(cls):
3036 def delete_all_cache(cls):
3037 """
3037 """
3038 Delete all cache keys from database.
3038 Delete all cache keys from database.
3039 Should only be run when all instances are down and all entries
3039 Should only be run when all instances are down and all entries
3040 thus stale.
3040 thus stale.
3041 """
3041 """
3042 cls.query().delete()
3042 cls.query().delete()
3043 Session().commit()
3043 Session().commit()
3044
3044
3045 @classmethod
3045 @classmethod
3046 def get_cache_key(cls, repo_name, cache_type):
3046 def get_cache_key(cls, repo_name, cache_type):
3047 """
3047 """
3048
3048
3049 Generate a cache key for this process of RhodeCode instance.
3049 Generate a cache key for this process of RhodeCode instance.
3050 Prefix most likely will be process id or maybe explicitly set
3050 Prefix most likely will be process id or maybe explicitly set
3051 instance_id from .ini file.
3051 instance_id from .ini file.
3052 """
3052 """
3053 import rhodecode
3053 import rhodecode
3054 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3054 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3055
3055
3056 repo_as_unicode = safe_unicode(repo_name)
3056 repo_as_unicode = safe_unicode(repo_name)
3057 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3057 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3058 if cache_type else repo_as_unicode
3058 if cache_type else repo_as_unicode
3059
3059
3060 return u'{}{}'.format(prefix, key)
3060 return u'{}{}'.format(prefix, key)
3061
3061
3062 @classmethod
3062 @classmethod
3063 def set_invalidate(cls, repo_name, delete=False):
3063 def set_invalidate(cls, repo_name, delete=False):
3064 """
3064 """
3065 Mark all caches of a repo as invalid in the database.
3065 Mark all caches of a repo as invalid in the database.
3066 """
3066 """
3067
3067
3068 try:
3068 try:
3069 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3069 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3070 if delete:
3070 if delete:
3071 log.debug('cache objects deleted for repo %s',
3071 log.debug('cache objects deleted for repo %s',
3072 safe_str(repo_name))
3072 safe_str(repo_name))
3073 qry.delete()
3073 qry.delete()
3074 else:
3074 else:
3075 log.debug('cache objects marked as invalid for repo %s',
3075 log.debug('cache objects marked as invalid for repo %s',
3076 safe_str(repo_name))
3076 safe_str(repo_name))
3077 qry.update({"cache_active": False})
3077 qry.update({"cache_active": False})
3078
3078
3079 Session().commit()
3079 Session().commit()
3080 except Exception:
3080 except Exception:
3081 log.exception(
3081 log.exception(
3082 'Cache key invalidation failed for repository %s',
3082 'Cache key invalidation failed for repository %s',
3083 safe_str(repo_name))
3083 safe_str(repo_name))
3084 Session().rollback()
3084 Session().rollback()
3085
3085
3086 @classmethod
3086 @classmethod
3087 def get_active_cache(cls, cache_key):
3087 def get_active_cache(cls, cache_key):
3088 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3088 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3089 if inv_obj:
3089 if inv_obj:
3090 return inv_obj
3090 return inv_obj
3091 return None
3091 return None
3092
3092
3093
3093
3094 class ChangesetComment(Base, BaseModel):
3094 class ChangesetComment(Base, BaseModel):
3095 __tablename__ = 'changeset_comments'
3095 __tablename__ = 'changeset_comments'
3096 __table_args__ = (
3096 __table_args__ = (
3097 Index('cc_revision_idx', 'revision'),
3097 Index('cc_revision_idx', 'revision'),
3098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3100 )
3100 )
3101
3101
3102 COMMENT_OUTDATED = u'comment_outdated'
3102 COMMENT_OUTDATED = u'comment_outdated'
3103 COMMENT_TYPE_NOTE = u'note'
3103 COMMENT_TYPE_NOTE = u'note'
3104 COMMENT_TYPE_TODO = u'todo'
3104 COMMENT_TYPE_TODO = u'todo'
3105 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3105 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3106
3106
3107 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3107 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3108 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3108 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3109 revision = Column('revision', String(40), nullable=True)
3109 revision = Column('revision', String(40), nullable=True)
3110 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3110 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3111 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3111 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3112 line_no = Column('line_no', Unicode(10), nullable=True)
3112 line_no = Column('line_no', Unicode(10), nullable=True)
3113 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3113 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3114 f_path = Column('f_path', Unicode(1000), nullable=True)
3114 f_path = Column('f_path', Unicode(1000), nullable=True)
3115 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3115 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3116 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3116 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3117 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3117 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3118 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3118 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3119 renderer = Column('renderer', Unicode(64), nullable=True)
3119 renderer = Column('renderer', Unicode(64), nullable=True)
3120 display_state = Column('display_state', Unicode(128), nullable=True)
3120 display_state = Column('display_state', Unicode(128), nullable=True)
3121
3121
3122 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3122 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3123 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3123 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3124 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3124 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3125 author = relationship('User', lazy='joined')
3125 author = relationship('User', lazy='joined')
3126 repo = relationship('Repository')
3126 repo = relationship('Repository')
3127 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3127 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3128 pull_request = relationship('PullRequest', lazy='joined')
3128 pull_request = relationship('PullRequest', lazy='joined')
3129 pull_request_version = relationship('PullRequestVersion')
3129 pull_request_version = relationship('PullRequestVersion')
3130
3130
3131 @classmethod
3131 @classmethod
3132 def get_users(cls, revision=None, pull_request_id=None):
3132 def get_users(cls, revision=None, pull_request_id=None):
3133 """
3133 """
3134 Returns user associated with this ChangesetComment. ie those
3134 Returns user associated with this ChangesetComment. ie those
3135 who actually commented
3135 who actually commented
3136
3136
3137 :param cls:
3137 :param cls:
3138 :param revision:
3138 :param revision:
3139 """
3139 """
3140 q = Session().query(User)\
3140 q = Session().query(User)\
3141 .join(ChangesetComment.author)
3141 .join(ChangesetComment.author)
3142 if revision:
3142 if revision:
3143 q = q.filter(cls.revision == revision)
3143 q = q.filter(cls.revision == revision)
3144 elif pull_request_id:
3144 elif pull_request_id:
3145 q = q.filter(cls.pull_request_id == pull_request_id)
3145 q = q.filter(cls.pull_request_id == pull_request_id)
3146 return q.all()
3146 return q.all()
3147
3147
3148 @classmethod
3148 @classmethod
3149 def get_index_from_version(cls, pr_version, versions):
3149 def get_index_from_version(cls, pr_version, versions):
3150 num_versions = [x.pull_request_version_id for x in versions]
3150 num_versions = [x.pull_request_version_id for x in versions]
3151 try:
3151 try:
3152 return num_versions.index(pr_version) +1
3152 return num_versions.index(pr_version) +1
3153 except (IndexError, ValueError):
3153 except (IndexError, ValueError):
3154 return
3154 return
3155
3155
3156 @property
3156 @property
3157 def outdated(self):
3157 def outdated(self):
3158 return self.display_state == self.COMMENT_OUTDATED
3158 return self.display_state == self.COMMENT_OUTDATED
3159
3159
3160 def outdated_at_version(self, version):
3160 def outdated_at_version(self, version):
3161 """
3161 """
3162 Checks if comment is outdated for given pull request version
3162 Checks if comment is outdated for given pull request version
3163 """
3163 """
3164 return self.outdated and self.pull_request_version_id != version
3164 return self.outdated and self.pull_request_version_id != version
3165
3165
3166 def older_than_version(self, version):
3166 def older_than_version(self, version):
3167 """
3167 """
3168 Checks if comment is made from previous version than given
3168 Checks if comment is made from previous version than given
3169 """
3169 """
3170 if version is None:
3170 if version is None:
3171 return self.pull_request_version_id is not None
3171 return self.pull_request_version_id is not None
3172
3172
3173 return self.pull_request_version_id < version
3173 return self.pull_request_version_id < version
3174
3174
3175 @property
3175 @property
3176 def resolved(self):
3176 def resolved(self):
3177 return self.resolved_by[0] if self.resolved_by else None
3177 return self.resolved_by[0] if self.resolved_by else None
3178
3178
3179 @property
3179 @property
3180 def is_todo(self):
3180 def is_todo(self):
3181 return self.comment_type == self.COMMENT_TYPE_TODO
3181 return self.comment_type == self.COMMENT_TYPE_TODO
3182
3182
3183 @property
3183 @property
3184 def is_inline(self):
3184 def is_inline(self):
3185 return self.line_no and self.f_path
3185 return self.line_no and self.f_path
3186
3186
3187 def get_index_version(self, versions):
3187 def get_index_version(self, versions):
3188 return self.get_index_from_version(
3188 return self.get_index_from_version(
3189 self.pull_request_version_id, versions)
3189 self.pull_request_version_id, versions)
3190
3190
3191 def __repr__(self):
3191 def __repr__(self):
3192 if self.comment_id:
3192 if self.comment_id:
3193 return '<DB:Comment #%s>' % self.comment_id
3193 return '<DB:Comment #%s>' % self.comment_id
3194 else:
3194 else:
3195 return '<DB:Comment at %#x>' % id(self)
3195 return '<DB:Comment at %#x>' % id(self)
3196
3196
3197 def get_api_data(self):
3197 def get_api_data(self):
3198 comment = self
3198 comment = self
3199 data = {
3199 data = {
3200 'comment_id': comment.comment_id,
3200 'comment_id': comment.comment_id,
3201 'comment_type': comment.comment_type,
3201 'comment_type': comment.comment_type,
3202 'comment_text': comment.text,
3202 'comment_text': comment.text,
3203 'comment_status': comment.status_change,
3203 'comment_status': comment.status_change,
3204 'comment_f_path': comment.f_path,
3204 'comment_f_path': comment.f_path,
3205 'comment_lineno': comment.line_no,
3205 'comment_lineno': comment.line_no,
3206 'comment_author': comment.author,
3206 'comment_author': comment.author,
3207 'comment_created_on': comment.created_on
3207 'comment_created_on': comment.created_on
3208 }
3208 }
3209 return data
3209 return data
3210
3210
3211 def __json__(self):
3211 def __json__(self):
3212 data = dict()
3212 data = dict()
3213 data.update(self.get_api_data())
3213 data.update(self.get_api_data())
3214 return data
3214 return data
3215
3215
3216
3216
3217 class ChangesetStatus(Base, BaseModel):
3217 class ChangesetStatus(Base, BaseModel):
3218 __tablename__ = 'changeset_statuses'
3218 __tablename__ = 'changeset_statuses'
3219 __table_args__ = (
3219 __table_args__ = (
3220 Index('cs_revision_idx', 'revision'),
3220 Index('cs_revision_idx', 'revision'),
3221 Index('cs_version_idx', 'version'),
3221 Index('cs_version_idx', 'version'),
3222 UniqueConstraint('repo_id', 'revision', 'version'),
3222 UniqueConstraint('repo_id', 'revision', 'version'),
3223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3223 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3224 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3224 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3225 )
3225 )
3226 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3226 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3227 STATUS_APPROVED = 'approved'
3227 STATUS_APPROVED = 'approved'
3228 STATUS_REJECTED = 'rejected'
3228 STATUS_REJECTED = 'rejected'
3229 STATUS_UNDER_REVIEW = 'under_review'
3229 STATUS_UNDER_REVIEW = 'under_review'
3230
3230
3231 STATUSES = [
3231 STATUSES = [
3232 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3232 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3233 (STATUS_APPROVED, _("Approved")),
3233 (STATUS_APPROVED, _("Approved")),
3234 (STATUS_REJECTED, _("Rejected")),
3234 (STATUS_REJECTED, _("Rejected")),
3235 (STATUS_UNDER_REVIEW, _("Under Review")),
3235 (STATUS_UNDER_REVIEW, _("Under Review")),
3236 ]
3236 ]
3237
3237
3238 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3238 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3239 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3239 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3240 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3240 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3241 revision = Column('revision', String(40), nullable=False)
3241 revision = Column('revision', String(40), nullable=False)
3242 status = Column('status', String(128), nullable=False, default=DEFAULT)
3242 status = Column('status', String(128), nullable=False, default=DEFAULT)
3243 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3243 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3244 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3244 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3245 version = Column('version', Integer(), nullable=False, default=0)
3245 version = Column('version', Integer(), nullable=False, default=0)
3246 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3246 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3247
3247
3248 author = relationship('User', lazy='joined')
3248 author = relationship('User', lazy='joined')
3249 repo = relationship('Repository')
3249 repo = relationship('Repository')
3250 comment = relationship('ChangesetComment', lazy='joined')
3250 comment = relationship('ChangesetComment', lazy='joined')
3251 pull_request = relationship('PullRequest', lazy='joined')
3251 pull_request = relationship('PullRequest', lazy='joined')
3252
3252
3253 def __unicode__(self):
3253 def __unicode__(self):
3254 return u"<%s('%s[v%s]:%s')>" % (
3254 return u"<%s('%s[v%s]:%s')>" % (
3255 self.__class__.__name__,
3255 self.__class__.__name__,
3256 self.status, self.version, self.author
3256 self.status, self.version, self.author
3257 )
3257 )
3258
3258
3259 @classmethod
3259 @classmethod
3260 def get_status_lbl(cls, value):
3260 def get_status_lbl(cls, value):
3261 return dict(cls.STATUSES).get(value)
3261 return dict(cls.STATUSES).get(value)
3262
3262
3263 @property
3263 @property
3264 def status_lbl(self):
3264 def status_lbl(self):
3265 return ChangesetStatus.get_status_lbl(self.status)
3265 return ChangesetStatus.get_status_lbl(self.status)
3266
3266
3267 def get_api_data(self):
3267 def get_api_data(self):
3268 status = self
3268 status = self
3269 data = {
3269 data = {
3270 'status_id': status.changeset_status_id,
3270 'status_id': status.changeset_status_id,
3271 'status': status.status,
3271 'status': status.status,
3272 }
3272 }
3273 return data
3273 return data
3274
3274
3275 def __json__(self):
3275 def __json__(self):
3276 data = dict()
3276 data = dict()
3277 data.update(self.get_api_data())
3277 data.update(self.get_api_data())
3278 return data
3278 return data
3279
3279
3280
3280
3281 class _PullRequestBase(BaseModel):
3281 class _PullRequestBase(BaseModel):
3282 """
3282 """
3283 Common attributes of pull request and version entries.
3283 Common attributes of pull request and version entries.
3284 """
3284 """
3285
3285
3286 # .status values
3286 # .status values
3287 STATUS_NEW = u'new'
3287 STATUS_NEW = u'new'
3288 STATUS_OPEN = u'open'
3288 STATUS_OPEN = u'open'
3289 STATUS_CLOSED = u'closed'
3289 STATUS_CLOSED = u'closed'
3290
3290
3291 title = Column('title', Unicode(255), nullable=True)
3291 title = Column('title', Unicode(255), nullable=True)
3292 description = Column(
3292 description = Column(
3293 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3293 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3294 nullable=True)
3294 nullable=True)
3295 # new/open/closed status of pull request (not approve/reject/etc)
3295 # new/open/closed status of pull request (not approve/reject/etc)
3296 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3296 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3297 created_on = Column(
3297 created_on = Column(
3298 'created_on', DateTime(timezone=False), nullable=False,
3298 'created_on', DateTime(timezone=False), nullable=False,
3299 default=datetime.datetime.now)
3299 default=datetime.datetime.now)
3300 updated_on = Column(
3300 updated_on = Column(
3301 'updated_on', DateTime(timezone=False), nullable=False,
3301 'updated_on', DateTime(timezone=False), nullable=False,
3302 default=datetime.datetime.now)
3302 default=datetime.datetime.now)
3303
3303
3304 @declared_attr
3304 @declared_attr
3305 def user_id(cls):
3305 def user_id(cls):
3306 return Column(
3306 return Column(
3307 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3307 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3308 unique=None)
3308 unique=None)
3309
3309
3310 # 500 revisions max
3310 # 500 revisions max
3311 _revisions = Column(
3311 _revisions = Column(
3312 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3312 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3313
3313
3314 @declared_attr
3314 @declared_attr
3315 def source_repo_id(cls):
3315 def source_repo_id(cls):
3316 # TODO: dan: rename column to source_repo_id
3316 # TODO: dan: rename column to source_repo_id
3317 return Column(
3317 return Column(
3318 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3318 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3319 nullable=False)
3319 nullable=False)
3320
3320
3321 source_ref = Column('org_ref', Unicode(255), nullable=False)
3321 source_ref = Column('org_ref', Unicode(255), nullable=False)
3322
3322
3323 @declared_attr
3323 @declared_attr
3324 def target_repo_id(cls):
3324 def target_repo_id(cls):
3325 # TODO: dan: rename column to target_repo_id
3325 # TODO: dan: rename column to target_repo_id
3326 return Column(
3326 return Column(
3327 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3327 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3328 nullable=False)
3328 nullable=False)
3329
3329
3330 target_ref = Column('other_ref', Unicode(255), nullable=False)
3330 target_ref = Column('other_ref', Unicode(255), nullable=False)
3331 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3331 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3332
3332
3333 # TODO: dan: rename column to last_merge_source_rev
3333 # TODO: dan: rename column to last_merge_source_rev
3334 _last_merge_source_rev = Column(
3334 _last_merge_source_rev = Column(
3335 'last_merge_org_rev', String(40), nullable=True)
3335 'last_merge_org_rev', String(40), nullable=True)
3336 # TODO: dan: rename column to last_merge_target_rev
3336 # TODO: dan: rename column to last_merge_target_rev
3337 _last_merge_target_rev = Column(
3337 _last_merge_target_rev = Column(
3338 'last_merge_other_rev', String(40), nullable=True)
3338 'last_merge_other_rev', String(40), nullable=True)
3339 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3339 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3340 merge_rev = Column('merge_rev', String(40), nullable=True)
3340 merge_rev = Column('merge_rev', String(40), nullable=True)
3341
3341
3342 reviewer_data = Column(
3342 reviewer_data = Column(
3343 'reviewer_data_json', MutationObj.as_mutable(
3343 'reviewer_data_json', MutationObj.as_mutable(
3344 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3344 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3345
3345
3346 @property
3346 @property
3347 def reviewer_data_json(self):
3347 def reviewer_data_json(self):
3348 return json.dumps(self.reviewer_data)
3348 return json.dumps(self.reviewer_data)
3349
3349
3350 @hybrid_property
3350 @hybrid_property
3351 def description_safe(self):
3351 def description_safe(self):
3352 from rhodecode.lib import helpers as h
3352 from rhodecode.lib import helpers as h
3353 return h.escape(self.description)
3353 return h.escape(self.description)
3354
3354
3355 @hybrid_property
3355 @hybrid_property
3356 def revisions(self):
3356 def revisions(self):
3357 return self._revisions.split(':') if self._revisions else []
3357 return self._revisions.split(':') if self._revisions else []
3358
3358
3359 @revisions.setter
3359 @revisions.setter
3360 def revisions(self, val):
3360 def revisions(self, val):
3361 self._revisions = ':'.join(val)
3361 self._revisions = ':'.join(val)
3362
3362
3363 @hybrid_property
3363 @hybrid_property
3364 def last_merge_status(self):
3364 def last_merge_status(self):
3365 return safe_int(self._last_merge_status)
3365 return safe_int(self._last_merge_status)
3366
3366
3367 @last_merge_status.setter
3367 @last_merge_status.setter
3368 def last_merge_status(self, val):
3368 def last_merge_status(self, val):
3369 self._last_merge_status = val
3369 self._last_merge_status = val
3370
3370
3371 @declared_attr
3371 @declared_attr
3372 def author(cls):
3372 def author(cls):
3373 return relationship('User', lazy='joined')
3373 return relationship('User', lazy='joined')
3374
3374
3375 @declared_attr
3375 @declared_attr
3376 def source_repo(cls):
3376 def source_repo(cls):
3377 return relationship(
3377 return relationship(
3378 'Repository',
3378 'Repository',
3379 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3379 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3380
3380
3381 @property
3381 @property
3382 def source_ref_parts(self):
3382 def source_ref_parts(self):
3383 return self.unicode_to_reference(self.source_ref)
3383 return self.unicode_to_reference(self.source_ref)
3384
3384
3385 @declared_attr
3385 @declared_attr
3386 def target_repo(cls):
3386 def target_repo(cls):
3387 return relationship(
3387 return relationship(
3388 'Repository',
3388 'Repository',
3389 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3389 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3390
3390
3391 @property
3391 @property
3392 def target_ref_parts(self):
3392 def target_ref_parts(self):
3393 return self.unicode_to_reference(self.target_ref)
3393 return self.unicode_to_reference(self.target_ref)
3394
3394
3395 @property
3395 @property
3396 def shadow_merge_ref(self):
3396 def shadow_merge_ref(self):
3397 return self.unicode_to_reference(self._shadow_merge_ref)
3397 return self.unicode_to_reference(self._shadow_merge_ref)
3398
3398
3399 @shadow_merge_ref.setter
3399 @shadow_merge_ref.setter
3400 def shadow_merge_ref(self, ref):
3400 def shadow_merge_ref(self, ref):
3401 self._shadow_merge_ref = self.reference_to_unicode(ref)
3401 self._shadow_merge_ref = self.reference_to_unicode(ref)
3402
3402
3403 def unicode_to_reference(self, raw):
3403 def unicode_to_reference(self, raw):
3404 """
3404 """
3405 Convert a unicode (or string) to a reference object.
3405 Convert a unicode (or string) to a reference object.
3406 If unicode evaluates to False it returns None.
3406 If unicode evaluates to False it returns None.
3407 """
3407 """
3408 if raw:
3408 if raw:
3409 refs = raw.split(':')
3409 refs = raw.split(':')
3410 return Reference(*refs)
3410 return Reference(*refs)
3411 else:
3411 else:
3412 return None
3412 return None
3413
3413
3414 def reference_to_unicode(self, ref):
3414 def reference_to_unicode(self, ref):
3415 """
3415 """
3416 Convert a reference object to unicode.
3416 Convert a reference object to unicode.
3417 If reference is None it returns None.
3417 If reference is None it returns None.
3418 """
3418 """
3419 if ref:
3419 if ref:
3420 return u':'.join(ref)
3420 return u':'.join(ref)
3421 else:
3421 else:
3422 return None
3422 return None
3423
3423
3424 def get_api_data(self, with_merge_state=True):
3424 def get_api_data(self, with_merge_state=True):
3425 from rhodecode.model.pull_request import PullRequestModel
3425 from rhodecode.model.pull_request import PullRequestModel
3426
3426
3427 pull_request = self
3427 pull_request = self
3428 if with_merge_state:
3428 if with_merge_state:
3429 merge_status = PullRequestModel().merge_status(pull_request)
3429 merge_status = PullRequestModel().merge_status(pull_request)
3430 merge_state = {
3430 merge_state = {
3431 'status': merge_status[0],
3431 'status': merge_status[0],
3432 'message': safe_unicode(merge_status[1]),
3432 'message': safe_unicode(merge_status[1]),
3433 }
3433 }
3434 else:
3434 else:
3435 merge_state = {'status': 'not_available',
3435 merge_state = {'status': 'not_available',
3436 'message': 'not_available'}
3436 'message': 'not_available'}
3437
3437
3438 merge_data = {
3438 merge_data = {
3439 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3439 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3440 'reference': (
3440 'reference': (
3441 pull_request.shadow_merge_ref._asdict()
3441 pull_request.shadow_merge_ref._asdict()
3442 if pull_request.shadow_merge_ref else None),
3442 if pull_request.shadow_merge_ref else None),
3443 }
3443 }
3444
3444
3445 data = {
3445 data = {
3446 'pull_request_id': pull_request.pull_request_id,
3446 'pull_request_id': pull_request.pull_request_id,
3447 'url': PullRequestModel().get_url(pull_request),
3447 'url': PullRequestModel().get_url(pull_request),
3448 'title': pull_request.title,
3448 'title': pull_request.title,
3449 'description': pull_request.description,
3449 'description': pull_request.description,
3450 'status': pull_request.status,
3450 'status': pull_request.status,
3451 'created_on': pull_request.created_on,
3451 'created_on': pull_request.created_on,
3452 'updated_on': pull_request.updated_on,
3452 'updated_on': pull_request.updated_on,
3453 'commit_ids': pull_request.revisions,
3453 'commit_ids': pull_request.revisions,
3454 'review_status': pull_request.calculated_review_status(),
3454 'review_status': pull_request.calculated_review_status(),
3455 'mergeable': merge_state,
3455 'mergeable': merge_state,
3456 'source': {
3456 'source': {
3457 'clone_url': pull_request.source_repo.clone_url(),
3457 'clone_url': pull_request.source_repo.clone_url(),
3458 'repository': pull_request.source_repo.repo_name,
3458 'repository': pull_request.source_repo.repo_name,
3459 'reference': {
3459 'reference': {
3460 'name': pull_request.source_ref_parts.name,
3460 'name': pull_request.source_ref_parts.name,
3461 'type': pull_request.source_ref_parts.type,
3461 'type': pull_request.source_ref_parts.type,
3462 'commit_id': pull_request.source_ref_parts.commit_id,
3462 'commit_id': pull_request.source_ref_parts.commit_id,
3463 },
3463 },
3464 },
3464 },
3465 'target': {
3465 'target': {
3466 'clone_url': pull_request.target_repo.clone_url(),
3466 'clone_url': pull_request.target_repo.clone_url(),
3467 'repository': pull_request.target_repo.repo_name,
3467 'repository': pull_request.target_repo.repo_name,
3468 'reference': {
3468 'reference': {
3469 'name': pull_request.target_ref_parts.name,
3469 'name': pull_request.target_ref_parts.name,
3470 'type': pull_request.target_ref_parts.type,
3470 'type': pull_request.target_ref_parts.type,
3471 'commit_id': pull_request.target_ref_parts.commit_id,
3471 'commit_id': pull_request.target_ref_parts.commit_id,
3472 },
3472 },
3473 },
3473 },
3474 'merge': merge_data,
3474 'merge': merge_data,
3475 'author': pull_request.author.get_api_data(include_secrets=False,
3475 'author': pull_request.author.get_api_data(include_secrets=False,
3476 details='basic'),
3476 details='basic'),
3477 'reviewers': [
3477 'reviewers': [
3478 {
3478 {
3479 'user': reviewer.get_api_data(include_secrets=False,
3479 'user': reviewer.get_api_data(include_secrets=False,
3480 details='basic'),
3480 details='basic'),
3481 'reasons': reasons,
3481 'reasons': reasons,
3482 'review_status': st[0][1].status if st else 'not_reviewed',
3482 'review_status': st[0][1].status if st else 'not_reviewed',
3483 }
3483 }
3484 for reviewer, reasons, mandatory, st in
3484 for reviewer, reasons, mandatory, st in
3485 pull_request.reviewers_statuses()
3485 pull_request.reviewers_statuses()
3486 ]
3486 ]
3487 }
3487 }
3488
3488
3489 return data
3489 return data
3490
3490
3491
3491
3492 class PullRequest(Base, _PullRequestBase):
3492 class PullRequest(Base, _PullRequestBase):
3493 __tablename__ = 'pull_requests'
3493 __tablename__ = 'pull_requests'
3494 __table_args__ = (
3494 __table_args__ = (
3495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3497 )
3497 )
3498
3498
3499 pull_request_id = Column(
3499 pull_request_id = Column(
3500 'pull_request_id', Integer(), nullable=False, primary_key=True)
3500 'pull_request_id', Integer(), nullable=False, primary_key=True)
3501
3501
3502 def __repr__(self):
3502 def __repr__(self):
3503 if self.pull_request_id:
3503 if self.pull_request_id:
3504 return '<DB:PullRequest #%s>' % self.pull_request_id
3504 return '<DB:PullRequest #%s>' % self.pull_request_id
3505 else:
3505 else:
3506 return '<DB:PullRequest at %#x>' % id(self)
3506 return '<DB:PullRequest at %#x>' % id(self)
3507
3507
3508 reviewers = relationship('PullRequestReviewers',
3508 reviewers = relationship('PullRequestReviewers',
3509 cascade="all, delete, delete-orphan")
3509 cascade="all, delete, delete-orphan")
3510 statuses = relationship('ChangesetStatus',
3510 statuses = relationship('ChangesetStatus',
3511 cascade="all, delete, delete-orphan")
3511 cascade="all, delete, delete-orphan")
3512 comments = relationship('ChangesetComment',
3512 comments = relationship('ChangesetComment',
3513 cascade="all, delete, delete-orphan")
3513 cascade="all, delete, delete-orphan")
3514 versions = relationship('PullRequestVersion',
3514 versions = relationship('PullRequestVersion',
3515 cascade="all, delete, delete-orphan",
3515 cascade="all, delete, delete-orphan",
3516 lazy='dynamic')
3516 lazy='dynamic')
3517
3517
3518 @classmethod
3518 @classmethod
3519 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3519 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3520 internal_methods=None):
3520 internal_methods=None):
3521
3521
3522 class PullRequestDisplay(object):
3522 class PullRequestDisplay(object):
3523 """
3523 """
3524 Special object wrapper for showing PullRequest data via Versions
3524 Special object wrapper for showing PullRequest data via Versions
3525 It mimics PR object as close as possible. This is read only object
3525 It mimics PR object as close as possible. This is read only object
3526 just for display
3526 just for display
3527 """
3527 """
3528
3528
3529 def __init__(self, attrs, internal=None):
3529 def __init__(self, attrs, internal=None):
3530 self.attrs = attrs
3530 self.attrs = attrs
3531 # internal have priority over the given ones via attrs
3531 # internal have priority over the given ones via attrs
3532 self.internal = internal or ['versions']
3532 self.internal = internal or ['versions']
3533
3533
3534 def __getattr__(self, item):
3534 def __getattr__(self, item):
3535 if item in self.internal:
3535 if item in self.internal:
3536 return getattr(self, item)
3536 return getattr(self, item)
3537 try:
3537 try:
3538 return self.attrs[item]
3538 return self.attrs[item]
3539 except KeyError:
3539 except KeyError:
3540 raise AttributeError(
3540 raise AttributeError(
3541 '%s object has no attribute %s' % (self, item))
3541 '%s object has no attribute %s' % (self, item))
3542
3542
3543 def __repr__(self):
3543 def __repr__(self):
3544 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3544 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3545
3545
3546 def versions(self):
3546 def versions(self):
3547 return pull_request_obj.versions.order_by(
3547 return pull_request_obj.versions.order_by(
3548 PullRequestVersion.pull_request_version_id).all()
3548 PullRequestVersion.pull_request_version_id).all()
3549
3549
3550 def is_closed(self):
3550 def is_closed(self):
3551 return pull_request_obj.is_closed()
3551 return pull_request_obj.is_closed()
3552
3552
3553 @property
3553 @property
3554 def pull_request_version_id(self):
3554 def pull_request_version_id(self):
3555 return getattr(pull_request_obj, 'pull_request_version_id', None)
3555 return getattr(pull_request_obj, 'pull_request_version_id', None)
3556
3556
3557 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3557 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3558
3558
3559 attrs.author = StrictAttributeDict(
3559 attrs.author = StrictAttributeDict(
3560 pull_request_obj.author.get_api_data())
3560 pull_request_obj.author.get_api_data())
3561 if pull_request_obj.target_repo:
3561 if pull_request_obj.target_repo:
3562 attrs.target_repo = StrictAttributeDict(
3562 attrs.target_repo = StrictAttributeDict(
3563 pull_request_obj.target_repo.get_api_data())
3563 pull_request_obj.target_repo.get_api_data())
3564 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3564 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3565
3565
3566 if pull_request_obj.source_repo:
3566 if pull_request_obj.source_repo:
3567 attrs.source_repo = StrictAttributeDict(
3567 attrs.source_repo = StrictAttributeDict(
3568 pull_request_obj.source_repo.get_api_data())
3568 pull_request_obj.source_repo.get_api_data())
3569 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3569 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3570
3570
3571 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3571 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3572 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3572 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3573 attrs.revisions = pull_request_obj.revisions
3573 attrs.revisions = pull_request_obj.revisions
3574
3574
3575 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3575 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3576 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3576 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3577 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3577 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3578
3578
3579 return PullRequestDisplay(attrs, internal=internal_methods)
3579 return PullRequestDisplay(attrs, internal=internal_methods)
3580
3580
3581 def is_closed(self):
3581 def is_closed(self):
3582 return self.status == self.STATUS_CLOSED
3582 return self.status == self.STATUS_CLOSED
3583
3583
3584 def __json__(self):
3584 def __json__(self):
3585 return {
3585 return {
3586 'revisions': self.revisions,
3586 'revisions': self.revisions,
3587 }
3587 }
3588
3588
3589 def calculated_review_status(self):
3589 def calculated_review_status(self):
3590 from rhodecode.model.changeset_status import ChangesetStatusModel
3590 from rhodecode.model.changeset_status import ChangesetStatusModel
3591 return ChangesetStatusModel().calculated_review_status(self)
3591 return ChangesetStatusModel().calculated_review_status(self)
3592
3592
3593 def reviewers_statuses(self):
3593 def reviewers_statuses(self):
3594 from rhodecode.model.changeset_status import ChangesetStatusModel
3594 from rhodecode.model.changeset_status import ChangesetStatusModel
3595 return ChangesetStatusModel().reviewers_statuses(self)
3595 return ChangesetStatusModel().reviewers_statuses(self)
3596
3596
3597 @property
3597 @property
3598 def workspace_id(self):
3598 def workspace_id(self):
3599 from rhodecode.model.pull_request import PullRequestModel
3599 from rhodecode.model.pull_request import PullRequestModel
3600 return PullRequestModel()._workspace_id(self)
3600 return PullRequestModel()._workspace_id(self)
3601
3601
3602 def get_shadow_repo(self):
3602 def get_shadow_repo(self):
3603 workspace_id = self.workspace_id
3603 workspace_id = self.workspace_id
3604 vcs_obj = self.target_repo.scm_instance()
3604 vcs_obj = self.target_repo.scm_instance()
3605 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3605 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3606 workspace_id)
3606 workspace_id)
3607 return vcs_obj._get_shadow_instance(shadow_repository_path)
3607 return vcs_obj._get_shadow_instance(shadow_repository_path)
3608
3608
3609
3609
3610 class PullRequestVersion(Base, _PullRequestBase):
3610 class PullRequestVersion(Base, _PullRequestBase):
3611 __tablename__ = 'pull_request_versions'
3611 __tablename__ = 'pull_request_versions'
3612 __table_args__ = (
3612 __table_args__ = (
3613 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3613 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3614 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3614 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3615 )
3615 )
3616
3616
3617 pull_request_version_id = Column(
3617 pull_request_version_id = Column(
3618 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3618 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3619 pull_request_id = Column(
3619 pull_request_id = Column(
3620 'pull_request_id', Integer(),
3620 'pull_request_id', Integer(),
3621 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3621 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3622 pull_request = relationship('PullRequest')
3622 pull_request = relationship('PullRequest')
3623
3623
3624 def __repr__(self):
3624 def __repr__(self):
3625 if self.pull_request_version_id:
3625 if self.pull_request_version_id:
3626 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3626 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3627 else:
3627 else:
3628 return '<DB:PullRequestVersion at %#x>' % id(self)
3628 return '<DB:PullRequestVersion at %#x>' % id(self)
3629
3629
3630 @property
3630 @property
3631 def reviewers(self):
3631 def reviewers(self):
3632 return self.pull_request.reviewers
3632 return self.pull_request.reviewers
3633
3633
3634 @property
3634 @property
3635 def versions(self):
3635 def versions(self):
3636 return self.pull_request.versions
3636 return self.pull_request.versions
3637
3637
3638 def is_closed(self):
3638 def is_closed(self):
3639 # calculate from original
3639 # calculate from original
3640 return self.pull_request.status == self.STATUS_CLOSED
3640 return self.pull_request.status == self.STATUS_CLOSED
3641
3641
3642 def calculated_review_status(self):
3642 def calculated_review_status(self):
3643 return self.pull_request.calculated_review_status()
3643 return self.pull_request.calculated_review_status()
3644
3644
3645 def reviewers_statuses(self):
3645 def reviewers_statuses(self):
3646 return self.pull_request.reviewers_statuses()
3646 return self.pull_request.reviewers_statuses()
3647
3647
3648
3648
3649 class PullRequestReviewers(Base, BaseModel):
3649 class PullRequestReviewers(Base, BaseModel):
3650 __tablename__ = 'pull_request_reviewers'
3650 __tablename__ = 'pull_request_reviewers'
3651 __table_args__ = (
3651 __table_args__ = (
3652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3653 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3653 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3654 )
3654 )
3655
3655
3656 @hybrid_property
3656 @hybrid_property
3657 def reasons(self):
3657 def reasons(self):
3658 if not self._reasons:
3658 if not self._reasons:
3659 return []
3659 return []
3660 return self._reasons
3660 return self._reasons
3661
3661
3662 @reasons.setter
3662 @reasons.setter
3663 def reasons(self, val):
3663 def reasons(self, val):
3664 val = val or []
3664 val = val or []
3665 if any(not isinstance(x, basestring) for x in val):
3665 if any(not isinstance(x, basestring) for x in val):
3666 raise Exception('invalid reasons type, must be list of strings')
3666 raise Exception('invalid reasons type, must be list of strings')
3667 self._reasons = val
3667 self._reasons = val
3668
3668
3669 pull_requests_reviewers_id = Column(
3669 pull_requests_reviewers_id = Column(
3670 'pull_requests_reviewers_id', Integer(), nullable=False,
3670 'pull_requests_reviewers_id', Integer(), nullable=False,
3671 primary_key=True)
3671 primary_key=True)
3672 pull_request_id = Column(
3672 pull_request_id = Column(
3673 "pull_request_id", Integer(),
3673 "pull_request_id", Integer(),
3674 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3674 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3675 user_id = Column(
3675 user_id = Column(
3676 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3676 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3677 _reasons = Column(
3677 _reasons = Column(
3678 'reason', MutationList.as_mutable(
3678 'reason', MutationList.as_mutable(
3679 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3679 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3680 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3680 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3681 user = relationship('User')
3681 user = relationship('User')
3682 pull_request = relationship('PullRequest')
3682 pull_request = relationship('PullRequest')
3683
3683
3684
3684
3685 class Notification(Base, BaseModel):
3685 class Notification(Base, BaseModel):
3686 __tablename__ = 'notifications'
3686 __tablename__ = 'notifications'
3687 __table_args__ = (
3687 __table_args__ = (
3688 Index('notification_type_idx', 'type'),
3688 Index('notification_type_idx', 'type'),
3689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3690 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3690 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3691 )
3691 )
3692
3692
3693 TYPE_CHANGESET_COMMENT = u'cs_comment'
3693 TYPE_CHANGESET_COMMENT = u'cs_comment'
3694 TYPE_MESSAGE = u'message'
3694 TYPE_MESSAGE = u'message'
3695 TYPE_MENTION = u'mention'
3695 TYPE_MENTION = u'mention'
3696 TYPE_REGISTRATION = u'registration'
3696 TYPE_REGISTRATION = u'registration'
3697 TYPE_PULL_REQUEST = u'pull_request'
3697 TYPE_PULL_REQUEST = u'pull_request'
3698 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3698 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3699
3699
3700 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3700 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3701 subject = Column('subject', Unicode(512), nullable=True)
3701 subject = Column('subject', Unicode(512), nullable=True)
3702 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3702 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3703 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3703 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3704 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3704 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3705 type_ = Column('type', Unicode(255))
3705 type_ = Column('type', Unicode(255))
3706
3706
3707 created_by_user = relationship('User')
3707 created_by_user = relationship('User')
3708 notifications_to_users = relationship('UserNotification', lazy='joined',
3708 notifications_to_users = relationship('UserNotification', lazy='joined',
3709 cascade="all, delete, delete-orphan")
3709 cascade="all, delete, delete-orphan")
3710
3710
3711 @property
3711 @property
3712 def recipients(self):
3712 def recipients(self):
3713 return [x.user for x in UserNotification.query()\
3713 return [x.user for x in UserNotification.query()\
3714 .filter(UserNotification.notification == self)\
3714 .filter(UserNotification.notification == self)\
3715 .order_by(UserNotification.user_id.asc()).all()]
3715 .order_by(UserNotification.user_id.asc()).all()]
3716
3716
3717 @classmethod
3717 @classmethod
3718 def create(cls, created_by, subject, body, recipients, type_=None):
3718 def create(cls, created_by, subject, body, recipients, type_=None):
3719 if type_ is None:
3719 if type_ is None:
3720 type_ = Notification.TYPE_MESSAGE
3720 type_ = Notification.TYPE_MESSAGE
3721
3721
3722 notification = cls()
3722 notification = cls()
3723 notification.created_by_user = created_by
3723 notification.created_by_user = created_by
3724 notification.subject = subject
3724 notification.subject = subject
3725 notification.body = body
3725 notification.body = body
3726 notification.type_ = type_
3726 notification.type_ = type_
3727 notification.created_on = datetime.datetime.now()
3727 notification.created_on = datetime.datetime.now()
3728
3728
3729 for u in recipients:
3729 for u in recipients:
3730 assoc = UserNotification()
3730 assoc = UserNotification()
3731 assoc.notification = notification
3731 assoc.notification = notification
3732
3732
3733 # if created_by is inside recipients mark his notification
3733 # if created_by is inside recipients mark his notification
3734 # as read
3734 # as read
3735 if u.user_id == created_by.user_id:
3735 if u.user_id == created_by.user_id:
3736 assoc.read = True
3736 assoc.read = True
3737
3737
3738 u.notifications.append(assoc)
3738 u.notifications.append(assoc)
3739 Session().add(notification)
3739 Session().add(notification)
3740
3740
3741 return notification
3741 return notification
3742
3742
3743
3743
3744 class UserNotification(Base, BaseModel):
3744 class UserNotification(Base, BaseModel):
3745 __tablename__ = 'user_to_notification'
3745 __tablename__ = 'user_to_notification'
3746 __table_args__ = (
3746 __table_args__ = (
3747 UniqueConstraint('user_id', 'notification_id'),
3747 UniqueConstraint('user_id', 'notification_id'),
3748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3749 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3749 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3750 )
3750 )
3751 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3751 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3752 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3752 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3753 read = Column('read', Boolean, default=False)
3753 read = Column('read', Boolean, default=False)
3754 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3754 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3755
3755
3756 user = relationship('User', lazy="joined")
3756 user = relationship('User', lazy="joined")
3757 notification = relationship('Notification', lazy="joined",
3757 notification = relationship('Notification', lazy="joined",
3758 order_by=lambda: Notification.created_on.desc(),)
3758 order_by=lambda: Notification.created_on.desc(),)
3759
3759
3760 def mark_as_read(self):
3760 def mark_as_read(self):
3761 self.read = True
3761 self.read = True
3762 Session().add(self)
3762 Session().add(self)
3763
3763
3764
3764
3765 class Gist(Base, BaseModel):
3765 class Gist(Base, BaseModel):
3766 __tablename__ = 'gists'
3766 __tablename__ = 'gists'
3767 __table_args__ = (
3767 __table_args__ = (
3768 Index('g_gist_access_id_idx', 'gist_access_id'),
3768 Index('g_gist_access_id_idx', 'gist_access_id'),
3769 Index('g_created_on_idx', 'created_on'),
3769 Index('g_created_on_idx', 'created_on'),
3770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3771 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3771 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3772 )
3772 )
3773 GIST_PUBLIC = u'public'
3773 GIST_PUBLIC = u'public'
3774 GIST_PRIVATE = u'private'
3774 GIST_PRIVATE = u'private'
3775 DEFAULT_FILENAME = u'gistfile1.txt'
3775 DEFAULT_FILENAME = u'gistfile1.txt'
3776
3776
3777 ACL_LEVEL_PUBLIC = u'acl_public'
3777 ACL_LEVEL_PUBLIC = u'acl_public'
3778 ACL_LEVEL_PRIVATE = u'acl_private'
3778 ACL_LEVEL_PRIVATE = u'acl_private'
3779
3779
3780 gist_id = Column('gist_id', Integer(), primary_key=True)
3780 gist_id = Column('gist_id', Integer(), primary_key=True)
3781 gist_access_id = Column('gist_access_id', Unicode(250))
3781 gist_access_id = Column('gist_access_id', Unicode(250))
3782 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3782 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3783 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3783 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3784 gist_expires = Column('gist_expires', Float(53), nullable=False)
3784 gist_expires = Column('gist_expires', Float(53), nullable=False)
3785 gist_type = Column('gist_type', Unicode(128), nullable=False)
3785 gist_type = Column('gist_type', Unicode(128), nullable=False)
3786 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3786 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3787 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3787 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3788 acl_level = Column('acl_level', Unicode(128), nullable=True)
3788 acl_level = Column('acl_level', Unicode(128), nullable=True)
3789
3789
3790 owner = relationship('User')
3790 owner = relationship('User')
3791
3791
3792 def __repr__(self):
3792 def __repr__(self):
3793 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3793 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3794
3794
3795 @hybrid_property
3795 @hybrid_property
3796 def description_safe(self):
3796 def description_safe(self):
3797 from rhodecode.lib import helpers as h
3797 from rhodecode.lib import helpers as h
3798 return h.escape(self.gist_description)
3798 return h.escape(self.gist_description)
3799
3799
3800 @classmethod
3800 @classmethod
3801 def get_or_404(cls, id_):
3801 def get_or_404(cls, id_):
3802 from pyramid.httpexceptions import HTTPNotFound
3802 from pyramid.httpexceptions import HTTPNotFound
3803
3803
3804 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3804 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3805 if not res:
3805 if not res:
3806 raise HTTPNotFound()
3806 raise HTTPNotFound()
3807 return res
3807 return res
3808
3808
3809 @classmethod
3809 @classmethod
3810 def get_by_access_id(cls, gist_access_id):
3810 def get_by_access_id(cls, gist_access_id):
3811 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3811 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3812
3812
3813 def gist_url(self):
3813 def gist_url(self):
3814 from rhodecode.model.gist import GistModel
3814 from rhodecode.model.gist import GistModel
3815 return GistModel().get_url(self)
3815 return GistModel().get_url(self)
3816
3816
3817 @classmethod
3817 @classmethod
3818 def base_path(cls):
3818 def base_path(cls):
3819 """
3819 """
3820 Returns base path when all gists are stored
3820 Returns base path when all gists are stored
3821
3821
3822 :param cls:
3822 :param cls:
3823 """
3823 """
3824 from rhodecode.model.gist import GIST_STORE_LOC
3824 from rhodecode.model.gist import GIST_STORE_LOC
3825 q = Session().query(RhodeCodeUi)\
3825 q = Session().query(RhodeCodeUi)\
3826 .filter(RhodeCodeUi.ui_key == URL_SEP)
3826 .filter(RhodeCodeUi.ui_key == URL_SEP)
3827 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3827 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3828 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3828 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3829
3829
3830 def get_api_data(self):
3830 def get_api_data(self):
3831 """
3831 """
3832 Common function for generating gist related data for API
3832 Common function for generating gist related data for API
3833 """
3833 """
3834 gist = self
3834 gist = self
3835 data = {
3835 data = {
3836 'gist_id': gist.gist_id,
3836 'gist_id': gist.gist_id,
3837 'type': gist.gist_type,
3837 'type': gist.gist_type,
3838 'access_id': gist.gist_access_id,
3838 'access_id': gist.gist_access_id,
3839 'description': gist.gist_description,
3839 'description': gist.gist_description,
3840 'url': gist.gist_url(),
3840 'url': gist.gist_url(),
3841 'expires': gist.gist_expires,
3841 'expires': gist.gist_expires,
3842 'created_on': gist.created_on,
3842 'created_on': gist.created_on,
3843 'modified_at': gist.modified_at,
3843 'modified_at': gist.modified_at,
3844 'content': None,
3844 'content': None,
3845 'acl_level': gist.acl_level,
3845 'acl_level': gist.acl_level,
3846 }
3846 }
3847 return data
3847 return data
3848
3848
3849 def __json__(self):
3849 def __json__(self):
3850 data = dict(
3850 data = dict(
3851 )
3851 )
3852 data.update(self.get_api_data())
3852 data.update(self.get_api_data())
3853 return data
3853 return data
3854 # SCM functions
3854 # SCM functions
3855
3855
3856 def scm_instance(self, **kwargs):
3856 def scm_instance(self, **kwargs):
3857 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3857 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3858 return get_vcs_instance(
3858 return get_vcs_instance(
3859 repo_path=safe_str(full_repo_path), create=False)
3859 repo_path=safe_str(full_repo_path), create=False)
3860
3860
3861
3861
3862 class ExternalIdentity(Base, BaseModel):
3862 class ExternalIdentity(Base, BaseModel):
3863 __tablename__ = 'external_identities'
3863 __tablename__ = 'external_identities'
3864 __table_args__ = (
3864 __table_args__ = (
3865 Index('local_user_id_idx', 'local_user_id'),
3865 Index('local_user_id_idx', 'local_user_id'),
3866 Index('external_id_idx', 'external_id'),
3866 Index('external_id_idx', 'external_id'),
3867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3868 'mysql_charset': 'utf8'})
3868 'mysql_charset': 'utf8'})
3869
3869
3870 external_id = Column('external_id', Unicode(255), default=u'',
3870 external_id = Column('external_id', Unicode(255), default=u'',
3871 primary_key=True)
3871 primary_key=True)
3872 external_username = Column('external_username', Unicode(1024), default=u'')
3872 external_username = Column('external_username', Unicode(1024), default=u'')
3873 local_user_id = Column('local_user_id', Integer(),
3873 local_user_id = Column('local_user_id', Integer(),
3874 ForeignKey('users.user_id'), primary_key=True)
3874 ForeignKey('users.user_id'), primary_key=True)
3875 provider_name = Column('provider_name', Unicode(255), default=u'',
3875 provider_name = Column('provider_name', Unicode(255), default=u'',
3876 primary_key=True)
3876 primary_key=True)
3877 access_token = Column('access_token', String(1024), default=u'')
3877 access_token = Column('access_token', String(1024), default=u'')
3878 alt_token = Column('alt_token', String(1024), default=u'')
3878 alt_token = Column('alt_token', String(1024), default=u'')
3879 token_secret = Column('token_secret', String(1024), default=u'')
3879 token_secret = Column('token_secret', String(1024), default=u'')
3880
3880
3881 @classmethod
3881 @classmethod
3882 def by_external_id_and_provider(cls, external_id, provider_name,
3882 def by_external_id_and_provider(cls, external_id, provider_name,
3883 local_user_id=None):
3883 local_user_id=None):
3884 """
3884 """
3885 Returns ExternalIdentity instance based on search params
3885 Returns ExternalIdentity instance based on search params
3886
3886
3887 :param external_id:
3887 :param external_id:
3888 :param provider_name:
3888 :param provider_name:
3889 :return: ExternalIdentity
3889 :return: ExternalIdentity
3890 """
3890 """
3891 query = cls.query()
3891 query = cls.query()
3892 query = query.filter(cls.external_id == external_id)
3892 query = query.filter(cls.external_id == external_id)
3893 query = query.filter(cls.provider_name == provider_name)
3893 query = query.filter(cls.provider_name == provider_name)
3894 if local_user_id:
3894 if local_user_id:
3895 query = query.filter(cls.local_user_id == local_user_id)
3895 query = query.filter(cls.local_user_id == local_user_id)
3896 return query.first()
3896 return query.first()
3897
3897
3898 @classmethod
3898 @classmethod
3899 def user_by_external_id_and_provider(cls, external_id, provider_name):
3899 def user_by_external_id_and_provider(cls, external_id, provider_name):
3900 """
3900 """
3901 Returns User instance based on search params
3901 Returns User instance based on search params
3902
3902
3903 :param external_id:
3903 :param external_id:
3904 :param provider_name:
3904 :param provider_name:
3905 :return: User
3905 :return: User
3906 """
3906 """
3907 query = User.query()
3907 query = User.query()
3908 query = query.filter(cls.external_id == external_id)
3908 query = query.filter(cls.external_id == external_id)
3909 query = query.filter(cls.provider_name == provider_name)
3909 query = query.filter(cls.provider_name == provider_name)
3910 query = query.filter(User.user_id == cls.local_user_id)
3910 query = query.filter(User.user_id == cls.local_user_id)
3911 return query.first()
3911 return query.first()
3912
3912
3913 @classmethod
3913 @classmethod
3914 def by_local_user_id(cls, local_user_id):
3914 def by_local_user_id(cls, local_user_id):
3915 """
3915 """
3916 Returns all tokens for user
3916 Returns all tokens for user
3917
3917
3918 :param local_user_id:
3918 :param local_user_id:
3919 :return: ExternalIdentity
3919 :return: ExternalIdentity
3920 """
3920 """
3921 query = cls.query()
3921 query = cls.query()
3922 query = query.filter(cls.local_user_id == local_user_id)
3922 query = query.filter(cls.local_user_id == local_user_id)
3923 return query
3923 return query
3924
3924
3925
3925
3926 class Integration(Base, BaseModel):
3926 class Integration(Base, BaseModel):
3927 __tablename__ = 'integrations'
3927 __tablename__ = 'integrations'
3928 __table_args__ = (
3928 __table_args__ = (
3929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3931 )
3931 )
3932
3932
3933 integration_id = Column('integration_id', Integer(), primary_key=True)
3933 integration_id = Column('integration_id', Integer(), primary_key=True)
3934 integration_type = Column('integration_type', String(255))
3934 integration_type = Column('integration_type', String(255))
3935 enabled = Column('enabled', Boolean(), nullable=False)
3935 enabled = Column('enabled', Boolean(), nullable=False)
3936 name = Column('name', String(255), nullable=False)
3936 name = Column('name', String(255), nullable=False)
3937 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3937 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3938 default=False)
3938 default=False)
3939
3939
3940 settings = Column(
3940 settings = Column(
3941 'settings_json', MutationObj.as_mutable(
3941 'settings_json', MutationObj.as_mutable(
3942 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3942 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3943 repo_id = Column(
3943 repo_id = Column(
3944 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3944 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3945 nullable=True, unique=None, default=None)
3945 nullable=True, unique=None, default=None)
3946 repo = relationship('Repository', lazy='joined')
3946 repo = relationship('Repository', lazy='joined')
3947
3947
3948 repo_group_id = Column(
3948 repo_group_id = Column(
3949 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3949 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3950 nullable=True, unique=None, default=None)
3950 nullable=True, unique=None, default=None)
3951 repo_group = relationship('RepoGroup', lazy='joined')
3951 repo_group = relationship('RepoGroup', lazy='joined')
3952
3952
3953 @property
3953 @property
3954 def scope(self):
3954 def scope(self):
3955 if self.repo:
3955 if self.repo:
3956 return repr(self.repo)
3956 return repr(self.repo)
3957 if self.repo_group:
3957 if self.repo_group:
3958 if self.child_repos_only:
3958 if self.child_repos_only:
3959 return repr(self.repo_group) + ' (child repos only)'
3959 return repr(self.repo_group) + ' (child repos only)'
3960 else:
3960 else:
3961 return repr(self.repo_group) + ' (recursive)'
3961 return repr(self.repo_group) + ' (recursive)'
3962 if self.child_repos_only:
3962 if self.child_repos_only:
3963 return 'root_repos'
3963 return 'root_repos'
3964 return 'global'
3964 return 'global'
3965
3965
3966 def __repr__(self):
3966 def __repr__(self):
3967 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3967 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3968
3968
3969
3969
3970 class RepoReviewRuleUser(Base, BaseModel):
3970 class RepoReviewRuleUser(Base, BaseModel):
3971 __tablename__ = 'repo_review_rules_users'
3971 __tablename__ = 'repo_review_rules_users'
3972 __table_args__ = (
3972 __table_args__ = (
3973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3974 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3974 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3975 )
3975 )
3976 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3976 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3977 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3977 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3978 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3978 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3979 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3979 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3980 user = relationship('User')
3980 user = relationship('User')
3981
3981
3982 def rule_data(self):
3982 def rule_data(self):
3983 return {
3983 return {
3984 'mandatory': self.mandatory
3984 'mandatory': self.mandatory
3985 }
3985 }
3986
3986
3987
3987
3988 class RepoReviewRuleUserGroup(Base, BaseModel):
3988 class RepoReviewRuleUserGroup(Base, BaseModel):
3989 __tablename__ = 'repo_review_rules_users_groups'
3989 __tablename__ = 'repo_review_rules_users_groups'
3990 __table_args__ = (
3990 __table_args__ = (
3991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3993 )
3993 )
3994 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3994 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3995 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3995 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3996 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3996 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3997 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3997 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3998 users_group = relationship('UserGroup')
3998 users_group = relationship('UserGroup')
3999
3999
4000 def rule_data(self):
4000 def rule_data(self):
4001 return {
4001 return {
4002 'mandatory': self.mandatory
4002 'mandatory': self.mandatory
4003 }
4003 }
4004
4004
4005
4005
4006 class RepoReviewRule(Base, BaseModel):
4006 class RepoReviewRule(Base, BaseModel):
4007 __tablename__ = 'repo_review_rules'
4007 __tablename__ = 'repo_review_rules'
4008 __table_args__ = (
4008 __table_args__ = (
4009 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4009 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4010 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4010 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4011 )
4011 )
4012
4012
4013 repo_review_rule_id = Column(
4013 repo_review_rule_id = Column(
4014 'repo_review_rule_id', Integer(), primary_key=True)
4014 'repo_review_rule_id', Integer(), primary_key=True)
4015 repo_id = Column(
4015 repo_id = Column(
4016 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4016 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4017 repo = relationship('Repository', backref='review_rules')
4017 repo = relationship('Repository', backref='review_rules')
4018
4018
4019 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4019 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4020 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4020 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4021
4021
4022 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4022 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4023 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4023 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4024 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4024 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4025 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4025 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4026
4026
4027 rule_users = relationship('RepoReviewRuleUser')
4027 rule_users = relationship('RepoReviewRuleUser')
4028 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4028 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4029
4029
4030 @hybrid_property
4030 @hybrid_property
4031 def branch_pattern(self):
4031 def branch_pattern(self):
4032 return self._branch_pattern or '*'
4032 return self._branch_pattern or '*'
4033
4033
4034 def _validate_glob(self, value):
4034 def _validate_glob(self, value):
4035 re.compile('^' + glob2re(value) + '$')
4035 re.compile('^' + glob2re(value) + '$')
4036
4036
4037 @branch_pattern.setter
4037 @branch_pattern.setter
4038 def branch_pattern(self, value):
4038 def branch_pattern(self, value):
4039 self._validate_glob(value)
4039 self._validate_glob(value)
4040 self._branch_pattern = value or '*'
4040 self._branch_pattern = value or '*'
4041
4041
4042 @hybrid_property
4042 @hybrid_property
4043 def file_pattern(self):
4043 def file_pattern(self):
4044 return self._file_pattern or '*'
4044 return self._file_pattern or '*'
4045
4045
4046 @file_pattern.setter
4046 @file_pattern.setter
4047 def file_pattern(self, value):
4047 def file_pattern(self, value):
4048 self._validate_glob(value)
4048 self._validate_glob(value)
4049 self._file_pattern = value or '*'
4049 self._file_pattern = value or '*'
4050
4050
4051 def matches(self, branch, files_changed):
4051 def matches(self, branch, files_changed):
4052 """
4052 """
4053 Check if this review rule matches a branch/files in a pull request
4053 Check if this review rule matches a branch/files in a pull request
4054
4054
4055 :param branch: branch name for the commit
4055 :param branch: branch name for the commit
4056 :param files_changed: list of file paths changed in the pull request
4056 :param files_changed: list of file paths changed in the pull request
4057 """
4057 """
4058
4058
4059 branch = branch or ''
4059 branch = branch or ''
4060 files_changed = files_changed or []
4060 files_changed = files_changed or []
4061
4061
4062 branch_matches = True
4062 branch_matches = True
4063 if branch:
4063 if branch:
4064 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4064 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4065 branch_matches = bool(branch_regex.search(branch))
4065 branch_matches = bool(branch_regex.search(branch))
4066
4066
4067 files_matches = True
4067 files_matches = True
4068 if self.file_pattern != '*':
4068 if self.file_pattern != '*':
4069 files_matches = False
4069 files_matches = False
4070 file_regex = re.compile(glob2re(self.file_pattern))
4070 file_regex = re.compile(glob2re(self.file_pattern))
4071 for filename in files_changed:
4071 for filename in files_changed:
4072 if file_regex.search(filename):
4072 if file_regex.search(filename):
4073 files_matches = True
4073 files_matches = True
4074 break
4074 break
4075
4075
4076 return branch_matches and files_matches
4076 return branch_matches and files_matches
4077
4077
4078 @property
4078 @property
4079 def review_users(self):
4079 def review_users(self):
4080 """ Returns the users which this rule applies to """
4080 """ Returns the users which this rule applies to """
4081
4081
4082 users = collections.OrderedDict()
4082 users = collections.OrderedDict()
4083
4083
4084 for rule_user in self.rule_users:
4084 for rule_user in self.rule_users:
4085 if rule_user.user.active:
4085 if rule_user.user.active:
4086 if rule_user.user not in users:
4086 if rule_user.user not in users:
4087 users[rule_user.user.username] = {
4087 users[rule_user.user.username] = {
4088 'user': rule_user.user,
4088 'user': rule_user.user,
4089 'source': 'user',
4089 'source': 'user',
4090 'source_data': {},
4090 'source_data': {},
4091 'data': rule_user.rule_data()
4091 'data': rule_user.rule_data()
4092 }
4092 }
4093
4093
4094 for rule_user_group in self.rule_user_groups:
4094 for rule_user_group in self.rule_user_groups:
4095 source_data = {
4095 source_data = {
4096 'name': rule_user_group.users_group.users_group_name,
4096 'name': rule_user_group.users_group.users_group_name,
4097 'members': len(rule_user_group.users_group.members)
4097 'members': len(rule_user_group.users_group.members)
4098 }
4098 }
4099 for member in rule_user_group.users_group.members:
4099 for member in rule_user_group.users_group.members:
4100 if member.user.active:
4100 if member.user.active:
4101 users[member.user.username] = {
4101 users[member.user.username] = {
4102 'user': member.user,
4102 'user': member.user,
4103 'source': 'user_group',
4103 'source': 'user_group',
4104 'source_data': source_data,
4104 'source_data': source_data,
4105 'data': rule_user_group.rule_data()
4105 'data': rule_user_group.rule_data()
4106 }
4106 }
4107
4107
4108 return users
4108 return users
4109
4109
4110 def __repr__(self):
4110 def __repr__(self):
4111 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4111 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4112 self.repo_review_rule_id, self.repo)
4112 self.repo_review_rule_id, self.repo)
4113
4113
4114
4114
4115 class DbMigrateVersion(Base, BaseModel):
4115 class DbMigrateVersion(Base, BaseModel):
4116 __tablename__ = 'db_migrate_version'
4116 __tablename__ = 'db_migrate_version'
4117 __table_args__ = (
4117 __table_args__ = (
4118 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4118 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4119 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4119 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4120 )
4120 )
4121 repository_id = Column('repository_id', String(250), primary_key=True)
4121 repository_id = Column('repository_id', String(250), primary_key=True)
4122 repository_path = Column('repository_path', Text)
4122 repository_path = Column('repository_path', Text)
4123 version = Column('version', Integer)
4123 version = Column('version', Integer)
4124
4124
4125
4125
4126 class DbSession(Base, BaseModel):
4126 class DbSession(Base, BaseModel):
4127 __tablename__ = 'db_session'
4127 __tablename__ = 'db_session'
4128 __table_args__ = (
4128 __table_args__ = (
4129 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4129 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4130 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4130 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4131 )
4131 )
4132
4132
4133 def __repr__(self):
4133 def __repr__(self):
4134 return '<DB:DbSession({})>'.format(self.id)
4134 return '<DB:DbSession({})>'.format(self.id)
4135
4135
4136 id = Column('id', Integer())
4136 id = Column('id', Integer())
4137 namespace = Column('namespace', String(255), primary_key=True)
4137 namespace = Column('namespace', String(255), primary_key=True)
4138 accessed = Column('accessed', DateTime, nullable=False)
4138 accessed = Column('accessed', DateTime, nullable=False)
4139 created = Column('created', DateTime, nullable=False)
4139 created = Column('created', DateTime, nullable=False)
4140 data = Column('data', PickleType, nullable=False)
4140 data = Column('data', PickleType, nullable=False)
@@ -1,280 +1,280 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 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 Index schema for RhodeCode
22 Index schema for RhodeCode
23 """
23 """
24
24
25 from __future__ import absolute_import
25 from __future__ import absolute_import
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29
29
30 from whoosh import query as query_lib
30 from whoosh import query as query_lib
31 from whoosh.highlight import HtmlFormatter, ContextFragmenter
31 from whoosh.highlight import HtmlFormatter, ContextFragmenter
32 from whoosh.index import create_in, open_dir, exists_in, EmptyIndexError
32 from whoosh.index import create_in, open_dir, exists_in, EmptyIndexError
33 from whoosh.qparser import QueryParser, QueryParserError
33 from whoosh.qparser import QueryParser, QueryParserError
34
34
35 import rhodecode.lib.helpers as h
35 import rhodecode.lib.helpers as h
36 from rhodecode.lib.index import BaseSearch
36 from rhodecode.lib.index import BaseSearch
37 from rhodecode.lib.utils2 import safe_unicode
37 from rhodecode.lib.utils2 import safe_unicode
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 try:
42 try:
43 # we first try to import from rhodecode tools, fallback to copies if
43 # we first try to import from rhodecode tools, fallback to copies if
44 # we're unable to
44 # we're unable to
45 from rhodecode_tools.lib.fts_index.whoosh_schema import (
45 from rhodecode_tools.lib.fts_index.whoosh_schema import (
46 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
46 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
47 COMMIT_SCHEMA)
47 COMMIT_SCHEMA)
48 except ImportError:
48 except ImportError:
49 log.warning('rhodecode_tools schema not available, doing a fallback '
49 log.warning('rhodecode_tools schema not available, doing a fallback '
50 'import from `rhodecode.lib.index.whoosh_fallback_schema`')
50 'import from `rhodecode.lib.index.whoosh_fallback_schema`')
51 from rhodecode.lib.index.whoosh_fallback_schema import (
51 from rhodecode.lib.index.whoosh_fallback_schema import (
52 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
52 ANALYZER, FILE_INDEX_NAME, FILE_SCHEMA, COMMIT_INDEX_NAME,
53 COMMIT_SCHEMA)
53 COMMIT_SCHEMA)
54
54
55
55
56 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
56 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
57 FRAGMENTER = ContextFragmenter(200)
57 FRAGMENTER = ContextFragmenter(200)
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class Search(BaseSearch):
62 class Search(BaseSearch):
63 # this also shows in UI
63 # this also shows in UI
64 query_lang_doc = 'http://whoosh.readthedocs.io/en/latest/querylang.html'
64 query_lang_doc = 'http://whoosh.readthedocs.io/en/latest/querylang.html'
65 name = 'whoosh'
65 name = 'whoosh'
66
66
67 def __init__(self, config):
67 def __init__(self, config):
68 super(Search, self).__init__()
68 super(Search, self).__init__()
69 self.config = config
69 self.config = config
70 if not os.path.isdir(self.config['location']):
70 if not os.path.isdir(self.config['location']):
71 os.makedirs(self.config['location'])
71 os.makedirs(self.config['location'])
72
72
73 opener = create_in
73 opener = create_in
74 if exists_in(self.config['location'], indexname=FILE_INDEX_NAME):
74 if exists_in(self.config['location'], indexname=FILE_INDEX_NAME):
75 opener = open_dir
75 opener = open_dir
76 file_index = opener(self.config['location'], schema=FILE_SCHEMA,
76 file_index = opener(self.config['location'], schema=FILE_SCHEMA,
77 indexname=FILE_INDEX_NAME)
77 indexname=FILE_INDEX_NAME)
78
78
79 opener = create_in
79 opener = create_in
80 if exists_in(self.config['location'], indexname=COMMIT_INDEX_NAME):
80 if exists_in(self.config['location'], indexname=COMMIT_INDEX_NAME):
81 opener = open_dir
81 opener = open_dir
82 changeset_index = opener(self.config['location'], schema=COMMIT_SCHEMA,
82 changeset_index = opener(self.config['location'], schema=COMMIT_SCHEMA,
83 indexname=COMMIT_INDEX_NAME)
83 indexname=COMMIT_INDEX_NAME)
84
84
85 self.commit_schema = COMMIT_SCHEMA
85 self.commit_schema = COMMIT_SCHEMA
86 self.commit_index = changeset_index
86 self.commit_index = changeset_index
87 self.file_schema = FILE_SCHEMA
87 self.file_schema = FILE_SCHEMA
88 self.file_index = file_index
88 self.file_index = file_index
89 self.searcher = None
89 self.searcher = None
90
90
91 def cleanup(self):
91 def cleanup(self):
92 if self.searcher:
92 if self.searcher:
93 self.searcher.close()
93 self.searcher.close()
94
94
95 def _extend_query(self, query):
95 def _extend_query(self, query):
96 hashes = re.compile('([0-9a-f]{5,40})').findall(query)
96 hashes = re.compile('([0-9a-f]{5,40})').findall(query)
97 if hashes:
97 if hashes:
98 hashes_or_query = ' OR '.join('commit_id:%s*' % h for h in hashes)
98 hashes_or_query = ' OR '.join('commit_id:%s*' % h for h in hashes)
99 query = u'(%s) OR %s' % (query, hashes_or_query)
99 query = u'(%s) OR %s' % (query, hashes_or_query)
100 return query
100 return query
101
101
102 def search(self, query, document_type, search_user,
102 def search(self, query, document_type, search_user,
103 repo_name=None, requested_page=1, page_limit=10, sort=None,
103 repo_name=None, requested_page=1, page_limit=10, sort=None,
104 raise_on_exc=True):
104 raise_on_exc=True):
105
105
106 original_query = query
106 original_query = query
107 query = self._extend_query(query)
107 query = self._extend_query(query)
108
108
109 log.debug(u'QUERY: %s on %s', query, document_type)
109 log.debug(u'QUERY: %s on %s', query, document_type)
110 result = {
110 result = {
111 'results': [],
111 'results': [],
112 'count': 0,
112 'count': 0,
113 'error': None,
113 'error': None,
114 'runtime': 0
114 'runtime': 0
115 }
115 }
116 search_type, index_name, schema_defn = self._prepare_for_search(
116 search_type, index_name, schema_defn = self._prepare_for_search(
117 document_type)
117 document_type)
118 self._init_searcher(index_name)
118 self._init_searcher(index_name)
119 try:
119 try:
120 qp = QueryParser(search_type, schema=schema_defn)
120 qp = QueryParser(search_type, schema=schema_defn)
121 allowed_repos_filter = self._get_repo_filter(
121 allowed_repos_filter = self._get_repo_filter(
122 search_user, repo_name)
122 search_user, repo_name)
123 try:
123 try:
124 query = qp.parse(safe_unicode(query))
124 query = qp.parse(safe_unicode(query))
125 log.debug('query: %s (%s)', query, repr(query))
125 log.debug('query: %s (%s)', query, repr(query))
126
126
127 reverse, sortedby = False, None
127 reverse, sortedby = False, None
128 if search_type == 'message':
128 if search_type == 'message':
129 if sort == 'oldfirst':
129 if sort == 'oldfirst':
130 sortedby = 'date'
130 sortedby = 'date'
131 reverse = False
131 reverse = False
132 elif sort == 'newfirst':
132 elif sort == 'newfirst':
133 sortedby = 'date'
133 sortedby = 'date'
134 reverse = True
134 reverse = True
135
135
136 whoosh_results = self.searcher.search(
136 whoosh_results = self.searcher.search(
137 query, filter=allowed_repos_filter, limit=None,
137 query, filter=allowed_repos_filter, limit=None,
138 sortedby=sortedby, reverse=reverse)
138 sortedby=sortedby, reverse=reverse)
139
139
140 # fixes for 32k limit that whoosh uses for highlight
140 # fixes for 32k limit that whoosh uses for highlight
141 whoosh_results.fragmenter.charlimit = None
141 whoosh_results.fragmenter.charlimit = None
142 res_ln = whoosh_results.scored_length()
142 res_ln = whoosh_results.scored_length()
143 result['runtime'] = whoosh_results.runtime
143 result['runtime'] = whoosh_results.runtime
144 result['count'] = res_ln
144 result['count'] = res_ln
145 result['results'] = WhooshResultWrapper(
145 result['results'] = WhooshResultWrapper(
146 search_type, res_ln, whoosh_results)
146 search_type, res_ln, whoosh_results)
147
147
148 except QueryParserError:
148 except QueryParserError:
149 result['error'] = 'Invalid search query. Try quoting it.'
149 result['error'] = 'Invalid search query. Try quoting it.'
150 except (EmptyIndexError, IOError, OSError):
150 except (EmptyIndexError, IOError, OSError):
151 msg = 'There is no index to search in. Please run whoosh indexer'
151 msg = 'There is no index to search in. Please run whoosh indexer'
152 log.exception(msg)
152 log.exception(msg)
153 result['error'] = msg
153 result['error'] = msg
154 except Exception:
154 except Exception:
155 msg = 'An error occurred during this search operation'
155 msg = 'An error occurred during this search operation'
156 log.exception(msg)
156 log.exception(msg)
157 result['error'] = msg
157 result['error'] = msg
158
158
159 return result
159 return result
160
160
161 def statistics(self, translator):
161 def statistics(self, translator):
162 _ = translator
162 _ = translator
163 stats = [
163 stats = [
164 {'key': _('Index Type'), 'value': 'Whoosh'},
164 {'key': _('Index Type'), 'value': 'Whoosh'},
165 {'key': _('File Index'), 'value': str(self.file_index)},
165 {'key': _('File Index'), 'value': str(self.file_index)},
166 {'key': _('Indexed documents'),
166 {'key': _('Indexed documents'),
167 'value': self.file_index.doc_count()},
167 'value': self.file_index.doc_count()},
168 {'key': _('Last update'),
168 {'key': _('Last update'),
169 'value': h.time_to_datetime(self.file_index.last_modified())},
169 'value': h.time_to_datetime(self.file_index.last_modified())},
170 {'key': _('Commit index'), 'value': str(self.commit_index)},
170 {'key': _('Commit index'), 'value': str(self.commit_index)},
171 {'key': _('Indexed documents'),
171 {'key': _('Indexed documents'),
172 'value': str(self.commit_index.doc_count())},
172 'value': str(self.commit_index.doc_count())},
173 {'key': _('Last update'),
173 {'key': _('Last update'),
174 'value': h.time_to_datetime(self.commit_index.last_modified())}
174 'value': h.time_to_datetime(self.commit_index.last_modified())}
175 ]
175 ]
176 return stats
176 return stats
177
177
178 def _get_repo_filter(self, auth_user, repo_name):
178 def _get_repo_filter(self, auth_user, repo_name):
179
179
180 allowed_to_search = [
180 allowed_to_search = [
181 repo for repo, perm in
181 repo for repo, perm in
182 auth_user.permissions['repositories'].items()
182 auth_user.permissions['repositories'].items()
183 if perm != 'repository.none']
183 if perm != 'repository.none']
184
184
185 if repo_name:
185 if repo_name:
186 repo_filter = [query_lib.Term('repository', repo_name)]
186 repo_filter = [query_lib.Term('repository', repo_name)]
187
187
188 elif 'hg.admin' in auth_user.permissions.get('global', []):
188 elif 'hg.admin' in auth_user.permissions.get('global', []):
189 return None
189 return None
190
190
191 else:
191 else:
192 repo_filter = [query_lib.Term('repository', _rn)
192 repo_filter = [query_lib.Term('repository', _rn)
193 for _rn in allowed_to_search]
193 for _rn in allowed_to_search]
194 # in case we're not allowed to search anywhere, it's a trick
194 # in case we're not allowed to search anywhere, it's a trick
195 # to tell whoosh we're filtering, on ALL results
195 # to tell whoosh we're filtering, on ALL results
196 repo_filter = repo_filter or [query_lib.Term('repository', '')]
196 repo_filter = repo_filter or [query_lib.Term('repository', '')]
197
197
198 return query_lib.Or(repo_filter)
198 return query_lib.Or(repo_filter)
199
199
200 def _prepare_for_search(self, cur_type):
200 def _prepare_for_search(self, cur_type):
201 search_type = {
201 search_type = {
202 'content': 'content',
202 'content': 'content',
203 'commit': 'message',
203 'commit': 'message',
204 'path': 'path',
204 'path': 'path',
205 'repository': 'repository'
205 'repository': 'repository'
206 }.get(cur_type, 'content')
206 }.get(cur_type, 'content')
207
207
208 index_name = {
208 index_name = {
209 'content': FILE_INDEX_NAME,
209 'content': FILE_INDEX_NAME,
210 'commit': COMMIT_INDEX_NAME,
210 'commit': COMMIT_INDEX_NAME,
211 'path': FILE_INDEX_NAME
211 'path': FILE_INDEX_NAME
212 }.get(cur_type, FILE_INDEX_NAME)
212 }.get(cur_type, FILE_INDEX_NAME)
213
213
214 schema_defn = {
214 schema_defn = {
215 'content': self.file_schema,
215 'content': self.file_schema,
216 'commit': self.commit_schema,
216 'commit': self.commit_schema,
217 'path': self.file_schema
217 'path': self.file_schema
218 }.get(cur_type, self.file_schema)
218 }.get(cur_type, self.file_schema)
219
219
220 log.debug('IDX: %s', index_name)
220 log.debug('IDX: %s', index_name)
221 log.debug('SCHEMA: %s', schema_defn)
221 log.debug('SCHEMA: %s', schema_defn)
222 return search_type, index_name, schema_defn
222 return search_type, index_name, schema_defn
223
223
224 def _init_searcher(self, index_name):
224 def _init_searcher(self, index_name):
225 idx = open_dir(self.config['location'], indexname=index_name)
225 idx = open_dir(self.config['location'], indexname=index_name)
226 self.searcher = idx.searcher()
226 self.searcher = idx.searcher()
227 return self.searcher
227 return self.searcher
228
228
229
229
230 class WhooshResultWrapper(object):
230 class WhooshResultWrapper(object):
231 def __init__(self, search_type, total_hits, results):
231 def __init__(self, search_type, total_hits, results):
232 self.search_type = search_type
232 self.search_type = search_type
233 self.results = results
233 self.results = results
234 self.total_hits = total_hits
234 self.total_hits = total_hits
235
235
236 def __str__(self):
236 def __str__(self):
237 return '<%s at %s>' % (self.__class__.__name__, len(self))
237 return '<%s at %s>' % (self.__class__.__name__, len(self))
238
238
239 def __repr__(self):
239 def __repr__(self):
240 return self.__str__()
240 return self.__str__()
241
241
242 def __len__(self):
242 def __len__(self):
243 return self.total_hits
243 return self.total_hits
244
244
245 def __iter__(self):
245 def __iter__(self):
246 """
246 """
247 Allows Iteration over results,and lazy generate content
247 Allows Iteration over results,and lazy generate content
248
248
249 *Requires* implementation of ``__getitem__`` method.
249 *Requires* implementation of ``__getitem__`` method.
250 """
250 """
251 for hit in self.results:
251 for hit in self.results:
252 yield self.get_full_content(hit)
252 yield self.get_full_content(hit)
253
253
254 def __getitem__(self, key):
254 def __getitem__(self, key):
255 """
255 """
256 Slicing of resultWrapper
256 Slicing of resultWrapper
257 """
257 """
258 i, j = key.start, key.stop
258 i, j = key.start, key.stop
259 for hit in self.results[i:j]:
259 for hit in self.results[i:j]:
260 yield self.get_full_content(hit)
260 yield self.get_full_content(hit)
261
261
262 def get_full_content(self, hit):
262 def get_full_content(self, hit):
263 # TODO: marcink: this feels like an overkill, there's a lot of data
263 # TODO: marcink: this feels like an overkill, there's a lot of data
264 # inside hit object, and we don't need all
264 # inside hit object, and we don't need all
265 res = dict(hit)
265 res = dict(hit)
266
266
267 f_path = '' # noqa
267 f_path = '' # pragma: no cover
268 if self.search_type in ['content', 'path']:
268 if self.search_type in ['content', 'path']:
269 f_path = res['path'][len(res['repository']):]
269 f_path = res['path'][len(res['repository']):]
270 f_path = f_path.lstrip(os.sep)
270 f_path = f_path.lstrip(os.sep)
271
271
272 if self.search_type == 'content':
272 if self.search_type == 'content':
273 res.update({'content_short_hl': hit.highlights('content'),
273 res.update({'content_short_hl': hit.highlights('content'),
274 'f_path': f_path})
274 'f_path': f_path})
275 elif self.search_type == 'path':
275 elif self.search_type == 'path':
276 res.update({'f_path': f_path})
276 res.update({'f_path': f_path})
277 elif self.search_type == 'message':
277 elif self.search_type == 'message':
278 res.update({'message_hl': hit.highlights('message')})
278 res.update({'message_hl': hit.highlights('message')})
279
279
280 return res
280 return res
@@ -1,76 +1,76 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 from pyramid.compat import configparser
22 from pyramid.compat import configparser
23 from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # noqa
23 from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # pragma: no cover
24 from pyramid.scripting import prepare
24 from pyramid.scripting import prepare
25
25
26 from rhodecode.lib.request import Request
26 from rhodecode.lib.request import Request
27
27
28
28
29 def get_config(ini_path, **kwargs):
29 def get_config(ini_path, **kwargs):
30 parser = configparser.ConfigParser(**kwargs)
30 parser = configparser.ConfigParser(**kwargs)
31 parser.read(ini_path)
31 parser.read(ini_path)
32 return parser
32 return parser
33
33
34
34
35 def get_app_config(ini_path):
35 def get_app_config(ini_path):
36 from paste.deploy.loadwsgi import appconfig
36 from paste.deploy.loadwsgi import appconfig
37 return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd())
37 return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd())
38
38
39
39
40 class BootstrappedRequest(Request):
40 class BootstrappedRequest(Request):
41 """
41 """
42 Special version of Request Which has some available methods like in pyramid.
42 Special version of Request Which has some available methods like in pyramid.
43 Some code (used for template rendering) requires this, and we unsure it's present.
43 Some code (used for template rendering) requires this, and we unsure it's present.
44 """
44 """
45
45
46 def translate(self, msg):
46 def translate(self, msg):
47 return msg
47 return msg
48
48
49 def plularize(self, singular, plural, n):
49 def plularize(self, singular, plural, n):
50 return singular
50 return singular
51
51
52 def get_partial_renderer(self, tmpl_name):
52 def get_partial_renderer(self, tmpl_name):
53 from rhodecode.lib.partial_renderer import get_partial_renderer
53 from rhodecode.lib.partial_renderer import get_partial_renderer
54 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
54 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
55
55
56
56
57 def bootstrap(config_uri, request=None, options=None, env=None):
57 def bootstrap(config_uri, request=None, options=None, env=None):
58 if env:
58 if env:
59 os.environ.update(env)
59 os.environ.update(env)
60
60
61 config = get_config(config_uri)
61 config = get_config(config_uri)
62 base_url = 'http://rhodecode.local'
62 base_url = 'http://rhodecode.local'
63 try:
63 try:
64 base_url = config.get('app:main', 'app.base_url')
64 base_url = config.get('app:main', 'app.base_url')
65 except (configparser.NoSectionError, configparser.NoOptionError):
65 except (configparser.NoSectionError, configparser.NoOptionError):
66 pass
66 pass
67
67
68 request = request or BootstrappedRequest.blank('/', base_url=base_url)
68 request = request or BootstrappedRequest.blank('/', base_url=base_url)
69
69
70 return pyramid_bootstrap(config_uri, request=request, options=options)
70 return pyramid_bootstrap(config_uri, request=request, options=options)
71
71
72
72
73 def prepare_request(environ):
73 def prepare_request(environ):
74 request = Request.blank('/', environ=environ)
74 request = Request.blank('/', environ=environ)
75 prepare(request) # set pyramid threadlocal request
75 prepare(request) # set pyramid threadlocal request
76 return request
76 return request
@@ -1,253 +1,253 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 This serves as a drop in replacement for pycurl. It implements the pycurl Curl
22 This serves as a drop in replacement for pycurl. It implements the pycurl Curl
23 class in a way that is compatible with gevent.
23 class in a way that is compatible with gevent.
24 """
24 """
25
25
26
26
27 import logging
27 import logging
28 import gevent
28 import gevent
29 import pycurl
29 import pycurl
30 import greenlet
30 import greenlet
31
31
32 # Import everything from pycurl.
32 # Import everything from pycurl.
33 # This allows us to use this module as a drop in replacement of pycurl.
33 # This allows us to use this module as a drop in replacement of pycurl.
34 from pycurl import * # noqa
34 from pycurl import * # pragma: no cover
35
35
36 from gevent import core
36 from gevent import core
37 from gevent.hub import Waiter
37 from gevent.hub import Waiter
38
38
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class GeventCurlMulti(object):
43 class GeventCurlMulti(object):
44 """
44 """
45 Wrapper around pycurl.CurlMulti that integrates it into gevent's event
45 Wrapper around pycurl.CurlMulti that integrates it into gevent's event
46 loop.
46 loop.
47
47
48 Parts of this class are a modified version of code copied from the Tornado
48 Parts of this class are a modified version of code copied from the Tornado
49 Web Server project which is licensed under the Apache License, Version 2.0
49 Web Server project which is licensed under the Apache License, Version 2.0
50 (the "License"). To be more specific the code originates from this file:
50 (the "License"). To be more specific the code originates from this file:
51 https://github.com/tornadoweb/tornado/blob/stable/tornado/curl_httpclient.py
51 https://github.com/tornadoweb/tornado/blob/stable/tornado/curl_httpclient.py
52
52
53 This is the original license header of the origin:
53 This is the original license header of the origin:
54
54
55 Copyright 2009 Facebook
55 Copyright 2009 Facebook
56
56
57 Licensed under the Apache License, Version 2.0 (the "License"); you may
57 Licensed under the Apache License, Version 2.0 (the "License"); you may
58 not use this file except in compliance with the License. You may obtain
58 not use this file except in compliance with the License. You may obtain
59 a copy of the License at
59 a copy of the License at
60
60
61 http://www.apache.org/licenses/LICENSE-2.0
61 http://www.apache.org/licenses/LICENSE-2.0
62
62
63 Unless required by applicable law or agreed to in writing, software
63 Unless required by applicable law or agreed to in writing, software
64 distributed under the License is distributed on an "AS IS" BASIS,
64 distributed under the License is distributed on an "AS IS" BASIS,
65 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
65 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
66 implied. See the License for the specific language governing
66 implied. See the License for the specific language governing
67 permissions and limitations under the License.
67 permissions and limitations under the License.
68 """
68 """
69
69
70 def __init__(self, loop=None):
70 def __init__(self, loop=None):
71 self._watchers = {}
71 self._watchers = {}
72 self._timeout = None
72 self._timeout = None
73 self.loop = loop or gevent.get_hub().loop
73 self.loop = loop or gevent.get_hub().loop
74
74
75 # Setup curl's multi instance.
75 # Setup curl's multi instance.
76 self._curl_multi = pycurl.CurlMulti()
76 self._curl_multi = pycurl.CurlMulti()
77 self.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
77 self.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
78 self.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
78 self.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
79
79
80 def __getattr__(self, item):
80 def __getattr__(self, item):
81 """
81 """
82 The pycurl.CurlMulti class is final and we cannot subclass it.
82 The pycurl.CurlMulti class is final and we cannot subclass it.
83 Therefore we are wrapping it and forward everything to it here.
83 Therefore we are wrapping it and forward everything to it here.
84 """
84 """
85 return getattr(self._curl_multi, item)
85 return getattr(self._curl_multi, item)
86
86
87 def add_handle(self, curl):
87 def add_handle(self, curl):
88 """
88 """
89 Add handle variant that also takes care about the initial invocation of
89 Add handle variant that also takes care about the initial invocation of
90 socket action method. This is done by setting an immediate timeout.
90 socket action method. This is done by setting an immediate timeout.
91 """
91 """
92 result = self._curl_multi.add_handle(curl)
92 result = self._curl_multi.add_handle(curl)
93 self._set_timeout(0)
93 self._set_timeout(0)
94 return result
94 return result
95
95
96 def _handle_socket(self, event, fd, multi, data):
96 def _handle_socket(self, event, fd, multi, data):
97 """
97 """
98 Called by libcurl when it wants to change the file descriptors it cares
98 Called by libcurl when it wants to change the file descriptors it cares
99 about.
99 about.
100 """
100 """
101 event_map = {
101 event_map = {
102 pycurl.POLL_NONE: core.NONE,
102 pycurl.POLL_NONE: core.NONE,
103 pycurl.POLL_IN: core.READ,
103 pycurl.POLL_IN: core.READ,
104 pycurl.POLL_OUT: core.WRITE,
104 pycurl.POLL_OUT: core.WRITE,
105 pycurl.POLL_INOUT: core.READ | core.WRITE
105 pycurl.POLL_INOUT: core.READ | core.WRITE
106 }
106 }
107
107
108 if event == pycurl.POLL_REMOVE:
108 if event == pycurl.POLL_REMOVE:
109 watcher = self._watchers.pop(fd, None)
109 watcher = self._watchers.pop(fd, None)
110 if watcher is not None:
110 if watcher is not None:
111 watcher.stop()
111 watcher.stop()
112 else:
112 else:
113 gloop_event = event_map[event]
113 gloop_event = event_map[event]
114 watcher = self._watchers.get(fd)
114 watcher = self._watchers.get(fd)
115 if watcher is None:
115 if watcher is None:
116 watcher = self.loop.io(fd, gloop_event)
116 watcher = self.loop.io(fd, gloop_event)
117 watcher.start(self._handle_events, fd, pass_events=True)
117 watcher.start(self._handle_events, fd, pass_events=True)
118 self._watchers[fd] = watcher
118 self._watchers[fd] = watcher
119 else:
119 else:
120 if watcher.events != gloop_event:
120 if watcher.events != gloop_event:
121 watcher.stop()
121 watcher.stop()
122 watcher.events = gloop_event
122 watcher.events = gloop_event
123 watcher.start(self._handle_events, fd, pass_events=True)
123 watcher.start(self._handle_events, fd, pass_events=True)
124
124
125 def _set_timeout(self, msecs):
125 def _set_timeout(self, msecs):
126 """
126 """
127 Called by libcurl to schedule a timeout.
127 Called by libcurl to schedule a timeout.
128 """
128 """
129 if self._timeout is not None:
129 if self._timeout is not None:
130 self._timeout.stop()
130 self._timeout.stop()
131 self._timeout = self.loop.timer(msecs/1000.0)
131 self._timeout = self.loop.timer(msecs/1000.0)
132 self._timeout.start(self._handle_timeout)
132 self._timeout.start(self._handle_timeout)
133
133
134 def _handle_events(self, events, fd):
134 def _handle_events(self, events, fd):
135 action = 0
135 action = 0
136 if events & core.READ:
136 if events & core.READ:
137 action |= pycurl.CSELECT_IN
137 action |= pycurl.CSELECT_IN
138 if events & core.WRITE:
138 if events & core.WRITE:
139 action |= pycurl.CSELECT_OUT
139 action |= pycurl.CSELECT_OUT
140 while True:
140 while True:
141 try:
141 try:
142 ret, num_handles = self._curl_multi.socket_action(fd, action)
142 ret, num_handles = self._curl_multi.socket_action(fd, action)
143 except pycurl.error as e:
143 except pycurl.error as e:
144 ret = e.args[0]
144 ret = e.args[0]
145 if ret != pycurl.E_CALL_MULTI_PERFORM:
145 if ret != pycurl.E_CALL_MULTI_PERFORM:
146 break
146 break
147 self._finish_pending_requests()
147 self._finish_pending_requests()
148
148
149 def _handle_timeout(self):
149 def _handle_timeout(self):
150 """
150 """
151 Called by IOLoop when the requested timeout has passed.
151 Called by IOLoop when the requested timeout has passed.
152 """
152 """
153 if self._timeout is not None:
153 if self._timeout is not None:
154 self._timeout.stop()
154 self._timeout.stop()
155 self._timeout = None
155 self._timeout = None
156 while True:
156 while True:
157 try:
157 try:
158 ret, num_handles = self._curl_multi.socket_action(
158 ret, num_handles = self._curl_multi.socket_action(
159 pycurl.SOCKET_TIMEOUT, 0)
159 pycurl.SOCKET_TIMEOUT, 0)
160 except pycurl.error as e:
160 except pycurl.error as e:
161 ret = e.args[0]
161 ret = e.args[0]
162 if ret != pycurl.E_CALL_MULTI_PERFORM:
162 if ret != pycurl.E_CALL_MULTI_PERFORM:
163 break
163 break
164 self._finish_pending_requests()
164 self._finish_pending_requests()
165
165
166 # In theory, we shouldn't have to do this because curl will call
166 # In theory, we shouldn't have to do this because curl will call
167 # _set_timeout whenever the timeout changes. However, sometimes after
167 # _set_timeout whenever the timeout changes. However, sometimes after
168 # _handle_timeout we will need to reschedule immediately even though
168 # _handle_timeout we will need to reschedule immediately even though
169 # nothing has changed from curl's perspective. This is because when
169 # nothing has changed from curl's perspective. This is because when
170 # socket_action is called with SOCKET_TIMEOUT, libcurl decides
170 # socket_action is called with SOCKET_TIMEOUT, libcurl decides
171 # internally which timeouts need to be processed by using a monotonic
171 # internally which timeouts need to be processed by using a monotonic
172 # clock (where available) while tornado uses python's time.time() to
172 # clock (where available) while tornado uses python's time.time() to
173 # decide when timeouts have occurred. When those clocks disagree on
173 # decide when timeouts have occurred. When those clocks disagree on
174 # elapsed time (as they will whenever there is an NTP adjustment),
174 # elapsed time (as they will whenever there is an NTP adjustment),
175 # tornado might call _handle_timeout before libcurl is ready. After
175 # tornado might call _handle_timeout before libcurl is ready. After
176 # each timeout, resync the scheduled timeout with libcurl's current
176 # each timeout, resync the scheduled timeout with libcurl's current
177 # state.
177 # state.
178 new_timeout = self._curl_multi.timeout()
178 new_timeout = self._curl_multi.timeout()
179 if new_timeout >= 0:
179 if new_timeout >= 0:
180 self._set_timeout(new_timeout)
180 self._set_timeout(new_timeout)
181
181
182 def _finish_pending_requests(self):
182 def _finish_pending_requests(self):
183 """
183 """
184 Process any requests that were completed by the last call to
184 Process any requests that were completed by the last call to
185 multi.socket_action.
185 multi.socket_action.
186 """
186 """
187 while True:
187 while True:
188 num_q, ok_list, err_list = self._curl_multi.info_read()
188 num_q, ok_list, err_list = self._curl_multi.info_read()
189 for curl in ok_list:
189 for curl in ok_list:
190 curl.waiter.switch(None)
190 curl.waiter.switch(None)
191 for curl, errnum, errmsg in err_list:
191 for curl, errnum, errmsg in err_list:
192 curl.waiter.throw(Exception('%s %s' % (errnum, errmsg)))
192 curl.waiter.throw(Exception('%s %s' % (errnum, errmsg)))
193 if num_q == 0:
193 if num_q == 0:
194 break
194 break
195
195
196
196
197 class GeventCurl(object):
197 class GeventCurl(object):
198 """
198 """
199 Gevent compatible implementation of the pycurl.Curl class. Essentially a
199 Gevent compatible implementation of the pycurl.Curl class. Essentially a
200 wrapper around pycurl.Curl with a customized perform method. It uses the
200 wrapper around pycurl.Curl with a customized perform method. It uses the
201 GeventCurlMulti class to implement a blocking API to libcurl's "easy"
201 GeventCurlMulti class to implement a blocking API to libcurl's "easy"
202 interface.
202 interface.
203 """
203 """
204
204
205 # Reference to the GeventCurlMulti instance.
205 # Reference to the GeventCurlMulti instance.
206 _multi_instance = None
206 _multi_instance = None
207
207
208 def __init__(self):
208 def __init__(self):
209 self._curl = pycurl.Curl()
209 self._curl = pycurl.Curl()
210
210
211 def __getattr__(self, item):
211 def __getattr__(self, item):
212 """
212 """
213 The pycurl.Curl class is final and we cannot subclass it. Therefore we
213 The pycurl.Curl class is final and we cannot subclass it. Therefore we
214 are wrapping it and forward everything to it here.
214 are wrapping it and forward everything to it here.
215 """
215 """
216 return getattr(self._curl, item)
216 return getattr(self._curl, item)
217
217
218 @property
218 @property
219 def _multi(self):
219 def _multi(self):
220 """
220 """
221 Lazy property that returns the GeventCurlMulti instance. The value is
221 Lazy property that returns the GeventCurlMulti instance. The value is
222 cached as a class attribute. Therefore only one instance per process
222 cached as a class attribute. Therefore only one instance per process
223 exists.
223 exists.
224 """
224 """
225 if GeventCurl._multi_instance is None:
225 if GeventCurl._multi_instance is None:
226 GeventCurl._multi_instance = GeventCurlMulti()
226 GeventCurl._multi_instance = GeventCurlMulti()
227 return GeventCurl._multi_instance
227 return GeventCurl._multi_instance
228
228
229 def perform(self):
229 def perform(self):
230 """
230 """
231 This perform method is compatible with gevent because it uses gevent
231 This perform method is compatible with gevent because it uses gevent
232 synchronization mechanisms to wait for the request to finish.
232 synchronization mechanisms to wait for the request to finish.
233 """
233 """
234 if getattr(self._curl, 'waiter', None) is not None:
234 if getattr(self._curl, 'waiter', None) is not None:
235 current = greenlet.getcurrent()
235 current = greenlet.getcurrent()
236 msg = 'This curl object is already used by another greenlet, {}, \n' \
236 msg = 'This curl object is already used by another greenlet, {}, \n' \
237 'this is {}'.format(self._curl.waiter, current)
237 'this is {}'.format(self._curl.waiter, current)
238 raise Exception(msg)
238 raise Exception(msg)
239
239
240 waiter = self._curl.waiter = Waiter()
240 waiter = self._curl.waiter = Waiter()
241 try:
241 try:
242 self._multi.add_handle(self._curl)
242 self._multi.add_handle(self._curl)
243 try:
243 try:
244 return waiter.get()
244 return waiter.get()
245 finally:
245 finally:
246 self._multi.remove_handle(self._curl)
246 self._multi.remove_handle(self._curl)
247 finally:
247 finally:
248 del self._curl.waiter
248 del self._curl.waiter
249
249
250
250
251 # Curl is originally imported from pycurl. At this point we override it with
251 # Curl is originally imported from pycurl. At this point we override it with
252 # our custom implementation.
252 # our custom implementation.
253 Curl = GeventCurl
253 Curl = GeventCurl
@@ -1,4721 +1,4721 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from zope.cachedescriptors.property import Lazy as LazyProperty
50 from zope.cachedescriptors.property import Lazy as LazyProperty
51
51
52 from pyramid.threadlocal import get_current_request
52 from pyramid.threadlocal import get_current_request
53
53
54 from rhodecode.translation import _
54 from rhodecode.translation import _
55 from rhodecode.lib.vcs import get_vcs_instance
55 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 glob2re, StrictAttributeDict, cleaned_uri)
60 glob2re, StrictAttributeDict, cleaned_uri)
61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 JsonRaw
62 JsonRaw
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_user_sort(obj):
91 def display_user_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 display_user_group_sort(obj):
104 def display_user_group_sort(obj):
105 """
105 """
106 Sort function used to sort permissions in .permissions() function of
106 Sort function used to sort permissions in .permissions() function of
107 Repository, RepoGroup, UserGroup. Also it put the default user in front
107 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 of all other resources
108 of all other resources
109 """
109 """
110
110
111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 return prefix + obj.users_group_name
112 return prefix + obj.users_group_name
113
113
114
114
115 def _hash_key(k):
115 def _hash_key(k):
116 return sha1_safe(k)
116 return sha1_safe(k)
117
117
118
118
119 def in_filter_generator(qry, items, limit=500):
119 def in_filter_generator(qry, items, limit=500):
120 """
120 """
121 Splits IN() into multiple with OR
121 Splits IN() into multiple with OR
122 e.g.::
122 e.g.::
123 cnt = Repository.query().filter(
123 cnt = Repository.query().filter(
124 or_(
124 or_(
125 *in_filter_generator(Repository.repo_id, range(100000))
125 *in_filter_generator(Repository.repo_id, range(100000))
126 )).count()
126 )).count()
127 """
127 """
128 if not items:
128 if not items:
129 # empty list will cause empty query which might cause security issues
129 # empty list will cause empty query which might cause security issues
130 # this can lead to hidden unpleasant results
130 # this can lead to hidden unpleasant results
131 items = [-1]
131 items = [-1]
132
132
133 parts = []
133 parts = []
134 for chunk in xrange(0, len(items), limit):
134 for chunk in xrange(0, len(items), limit):
135 parts.append(
135 parts.append(
136 qry.in_(items[chunk: chunk + limit])
136 qry.in_(items[chunk: chunk + limit])
137 )
137 )
138
138
139 return parts
139 return parts
140
140
141
141
142 base_table_args = {
142 base_table_args = {
143 'extend_existing': True,
143 'extend_existing': True,
144 'mysql_engine': 'InnoDB',
144 'mysql_engine': 'InnoDB',
145 'mysql_charset': 'utf8',
145 'mysql_charset': 'utf8',
146 'sqlite_autoincrement': True
146 'sqlite_autoincrement': True
147 }
147 }
148
148
149
149
150 class EncryptedTextValue(TypeDecorator):
150 class EncryptedTextValue(TypeDecorator):
151 """
151 """
152 Special column for encrypted long text data, use like::
152 Special column for encrypted long text data, use like::
153
153
154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
155
155
156 This column is intelligent so if value is in unencrypted form it return
156 This column is intelligent so if value is in unencrypted form it return
157 unencrypted form, but on save it always encrypts
157 unencrypted form, but on save it always encrypts
158 """
158 """
159 impl = Text
159 impl = Text
160
160
161 def process_bind_param(self, value, dialect):
161 def process_bind_param(self, value, dialect):
162 if not value:
162 if not value:
163 return value
163 return value
164 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
164 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
165 # protect against double encrypting if someone manually starts
165 # protect against double encrypting if someone manually starts
166 # doing
166 # doing
167 raise ValueError('value needs to be in unencrypted format, ie. '
167 raise ValueError('value needs to be in unencrypted format, ie. '
168 'not starting with enc$aes')
168 'not starting with enc$aes')
169 return 'enc$aes_hmac$%s' % AESCipher(
169 return 'enc$aes_hmac$%s' % AESCipher(
170 ENCRYPTION_KEY, hmac=True).encrypt(value)
170 ENCRYPTION_KEY, hmac=True).encrypt(value)
171
171
172 def process_result_value(self, value, dialect):
172 def process_result_value(self, value, dialect):
173 import rhodecode
173 import rhodecode
174
174
175 if not value:
175 if not value:
176 return value
176 return value
177
177
178 parts = value.split('$', 3)
178 parts = value.split('$', 3)
179 if not len(parts) == 3:
179 if not len(parts) == 3:
180 # probably not encrypted values
180 # probably not encrypted values
181 return value
181 return value
182 else:
182 else:
183 if parts[0] != 'enc':
183 if parts[0] != 'enc':
184 # parts ok but without our header ?
184 # parts ok but without our header ?
185 return value
185 return value
186 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
186 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
187 'rhodecode.encrypted_values.strict') or True)
187 'rhodecode.encrypted_values.strict') or True)
188 # at that stage we know it's our encryption
188 # at that stage we know it's our encryption
189 if parts[1] == 'aes':
189 if parts[1] == 'aes':
190 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
190 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
191 elif parts[1] == 'aes_hmac':
191 elif parts[1] == 'aes_hmac':
192 decrypted_data = AESCipher(
192 decrypted_data = AESCipher(
193 ENCRYPTION_KEY, hmac=True,
193 ENCRYPTION_KEY, hmac=True,
194 strict_verification=enc_strict_mode).decrypt(parts[2])
194 strict_verification=enc_strict_mode).decrypt(parts[2])
195 else:
195 else:
196 raise ValueError(
196 raise ValueError(
197 'Encryption type part is wrong, must be `aes` '
197 'Encryption type part is wrong, must be `aes` '
198 'or `aes_hmac`, got `%s` instead' % (parts[1]))
198 'or `aes_hmac`, got `%s` instead' % (parts[1]))
199 return decrypted_data
199 return decrypted_data
200
200
201
201
202 class BaseModel(object):
202 class BaseModel(object):
203 """
203 """
204 Base Model for all classes
204 Base Model for all classes
205 """
205 """
206
206
207 @classmethod
207 @classmethod
208 def _get_keys(cls):
208 def _get_keys(cls):
209 """return column names for this model """
209 """return column names for this model """
210 return class_mapper(cls).c.keys()
210 return class_mapper(cls).c.keys()
211
211
212 def get_dict(self):
212 def get_dict(self):
213 """
213 """
214 return dict with keys and values corresponding
214 return dict with keys and values corresponding
215 to this model data """
215 to this model data """
216
216
217 d = {}
217 d = {}
218 for k in self._get_keys():
218 for k in self._get_keys():
219 d[k] = getattr(self, k)
219 d[k] = getattr(self, k)
220
220
221 # also use __json__() if present to get additional fields
221 # also use __json__() if present to get additional fields
222 _json_attr = getattr(self, '__json__', None)
222 _json_attr = getattr(self, '__json__', None)
223 if _json_attr:
223 if _json_attr:
224 # update with attributes from __json__
224 # update with attributes from __json__
225 if callable(_json_attr):
225 if callable(_json_attr):
226 _json_attr = _json_attr()
226 _json_attr = _json_attr()
227 for k, val in _json_attr.iteritems():
227 for k, val in _json_attr.iteritems():
228 d[k] = val
228 d[k] = val
229 return d
229 return d
230
230
231 def get_appstruct(self):
231 def get_appstruct(self):
232 """return list with keys and values tuples corresponding
232 """return list with keys and values tuples corresponding
233 to this model data """
233 to this model data """
234
234
235 lst = []
235 lst = []
236 for k in self._get_keys():
236 for k in self._get_keys():
237 lst.append((k, getattr(self, k),))
237 lst.append((k, getattr(self, k),))
238 return lst
238 return lst
239
239
240 def populate_obj(self, populate_dict):
240 def populate_obj(self, populate_dict):
241 """populate model with data from given populate_dict"""
241 """populate model with data from given populate_dict"""
242
242
243 for k in self._get_keys():
243 for k in self._get_keys():
244 if k in populate_dict:
244 if k in populate_dict:
245 setattr(self, k, populate_dict[k])
245 setattr(self, k, populate_dict[k])
246
246
247 @classmethod
247 @classmethod
248 def query(cls):
248 def query(cls):
249 return Session().query(cls)
249 return Session().query(cls)
250
250
251 @classmethod
251 @classmethod
252 def get(cls, id_):
252 def get(cls, id_):
253 if id_:
253 if id_:
254 return cls.query().get(id_)
254 return cls.query().get(id_)
255
255
256 @classmethod
256 @classmethod
257 def get_or_404(cls, id_):
257 def get_or_404(cls, id_):
258 from pyramid.httpexceptions import HTTPNotFound
258 from pyramid.httpexceptions import HTTPNotFound
259
259
260 try:
260 try:
261 id_ = int(id_)
261 id_ = int(id_)
262 except (TypeError, ValueError):
262 except (TypeError, ValueError):
263 raise HTTPNotFound()
263 raise HTTPNotFound()
264
264
265 res = cls.query().get(id_)
265 res = cls.query().get(id_)
266 if not res:
266 if not res:
267 raise HTTPNotFound()
267 raise HTTPNotFound()
268 return res
268 return res
269
269
270 @classmethod
270 @classmethod
271 def getAll(cls):
271 def getAll(cls):
272 # deprecated and left for backward compatibility
272 # deprecated and left for backward compatibility
273 return cls.get_all()
273 return cls.get_all()
274
274
275 @classmethod
275 @classmethod
276 def get_all(cls):
276 def get_all(cls):
277 return cls.query().all()
277 return cls.query().all()
278
278
279 @classmethod
279 @classmethod
280 def delete(cls, id_):
280 def delete(cls, id_):
281 obj = cls.query().get(id_)
281 obj = cls.query().get(id_)
282 Session().delete(obj)
282 Session().delete(obj)
283
283
284 @classmethod
284 @classmethod
285 def identity_cache(cls, session, attr_name, value):
285 def identity_cache(cls, session, attr_name, value):
286 exist_in_session = []
286 exist_in_session = []
287 for (item_cls, pkey), instance in session.identity_map.items():
287 for (item_cls, pkey), instance in session.identity_map.items():
288 if cls == item_cls and getattr(instance, attr_name) == value:
288 if cls == item_cls and getattr(instance, attr_name) == value:
289 exist_in_session.append(instance)
289 exist_in_session.append(instance)
290 if exist_in_session:
290 if exist_in_session:
291 if len(exist_in_session) == 1:
291 if len(exist_in_session) == 1:
292 return exist_in_session[0]
292 return exist_in_session[0]
293 log.exception(
293 log.exception(
294 'multiple objects with attr %s and '
294 'multiple objects with attr %s and '
295 'value %s found with same name: %r',
295 'value %s found with same name: %r',
296 attr_name, value, exist_in_session)
296 attr_name, value, exist_in_session)
297
297
298 def __repr__(self):
298 def __repr__(self):
299 if hasattr(self, '__unicode__'):
299 if hasattr(self, '__unicode__'):
300 # python repr needs to return str
300 # python repr needs to return str
301 try:
301 try:
302 return safe_str(self.__unicode__())
302 return safe_str(self.__unicode__())
303 except UnicodeDecodeError:
303 except UnicodeDecodeError:
304 pass
304 pass
305 return '<DB:%s>' % (self.__class__.__name__)
305 return '<DB:%s>' % (self.__class__.__name__)
306
306
307
307
308 class RhodeCodeSetting(Base, BaseModel):
308 class RhodeCodeSetting(Base, BaseModel):
309 __tablename__ = 'rhodecode_settings'
309 __tablename__ = 'rhodecode_settings'
310 __table_args__ = (
310 __table_args__ = (
311 UniqueConstraint('app_settings_name'),
311 UniqueConstraint('app_settings_name'),
312 base_table_args
312 base_table_args
313 )
313 )
314
314
315 SETTINGS_TYPES = {
315 SETTINGS_TYPES = {
316 'str': safe_str,
316 'str': safe_str,
317 'int': safe_int,
317 'int': safe_int,
318 'unicode': safe_unicode,
318 'unicode': safe_unicode,
319 'bool': str2bool,
319 'bool': str2bool,
320 'list': functools.partial(aslist, sep=',')
320 'list': functools.partial(aslist, sep=',')
321 }
321 }
322 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
322 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
323 GLOBAL_CONF_KEY = 'app_settings'
323 GLOBAL_CONF_KEY = 'app_settings'
324
324
325 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
326 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
327 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
327 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
328 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
328 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
329
329
330 def __init__(self, key='', val='', type='unicode'):
330 def __init__(self, key='', val='', type='unicode'):
331 self.app_settings_name = key
331 self.app_settings_name = key
332 self.app_settings_type = type
332 self.app_settings_type = type
333 self.app_settings_value = val
333 self.app_settings_value = val
334
334
335 @validates('_app_settings_value')
335 @validates('_app_settings_value')
336 def validate_settings_value(self, key, val):
336 def validate_settings_value(self, key, val):
337 assert type(val) == unicode
337 assert type(val) == unicode
338 return val
338 return val
339
339
340 @hybrid_property
340 @hybrid_property
341 def app_settings_value(self):
341 def app_settings_value(self):
342 v = self._app_settings_value
342 v = self._app_settings_value
343 _type = self.app_settings_type
343 _type = self.app_settings_type
344 if _type:
344 if _type:
345 _type = self.app_settings_type.split('.')[0]
345 _type = self.app_settings_type.split('.')[0]
346 # decode the encrypted value
346 # decode the encrypted value
347 if 'encrypted' in self.app_settings_type:
347 if 'encrypted' in self.app_settings_type:
348 cipher = EncryptedTextValue()
348 cipher = EncryptedTextValue()
349 v = safe_unicode(cipher.process_result_value(v, None))
349 v = safe_unicode(cipher.process_result_value(v, None))
350
350
351 converter = self.SETTINGS_TYPES.get(_type) or \
351 converter = self.SETTINGS_TYPES.get(_type) or \
352 self.SETTINGS_TYPES['unicode']
352 self.SETTINGS_TYPES['unicode']
353 return converter(v)
353 return converter(v)
354
354
355 @app_settings_value.setter
355 @app_settings_value.setter
356 def app_settings_value(self, val):
356 def app_settings_value(self, val):
357 """
357 """
358 Setter that will always make sure we use unicode in app_settings_value
358 Setter that will always make sure we use unicode in app_settings_value
359
359
360 :param val:
360 :param val:
361 """
361 """
362 val = safe_unicode(val)
362 val = safe_unicode(val)
363 # encode the encrypted value
363 # encode the encrypted value
364 if 'encrypted' in self.app_settings_type:
364 if 'encrypted' in self.app_settings_type:
365 cipher = EncryptedTextValue()
365 cipher = EncryptedTextValue()
366 val = safe_unicode(cipher.process_bind_param(val, None))
366 val = safe_unicode(cipher.process_bind_param(val, None))
367 self._app_settings_value = val
367 self._app_settings_value = val
368
368
369 @hybrid_property
369 @hybrid_property
370 def app_settings_type(self):
370 def app_settings_type(self):
371 return self._app_settings_type
371 return self._app_settings_type
372
372
373 @app_settings_type.setter
373 @app_settings_type.setter
374 def app_settings_type(self, val):
374 def app_settings_type(self, val):
375 if val.split('.')[0] not in self.SETTINGS_TYPES:
375 if val.split('.')[0] not in self.SETTINGS_TYPES:
376 raise Exception('type must be one of %s got %s'
376 raise Exception('type must be one of %s got %s'
377 % (self.SETTINGS_TYPES.keys(), val))
377 % (self.SETTINGS_TYPES.keys(), val))
378 self._app_settings_type = val
378 self._app_settings_type = val
379
379
380 @classmethod
380 @classmethod
381 def get_by_prefix(cls, prefix):
381 def get_by_prefix(cls, prefix):
382 return RhodeCodeSetting.query()\
382 return RhodeCodeSetting.query()\
383 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
383 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
384 .all()
384 .all()
385
385
386 def __unicode__(self):
386 def __unicode__(self):
387 return u"<%s('%s:%s[%s]')>" % (
387 return u"<%s('%s:%s[%s]')>" % (
388 self.__class__.__name__,
388 self.__class__.__name__,
389 self.app_settings_name, self.app_settings_value,
389 self.app_settings_name, self.app_settings_value,
390 self.app_settings_type
390 self.app_settings_type
391 )
391 )
392
392
393
393
394 class RhodeCodeUi(Base, BaseModel):
394 class RhodeCodeUi(Base, BaseModel):
395 __tablename__ = 'rhodecode_ui'
395 __tablename__ = 'rhodecode_ui'
396 __table_args__ = (
396 __table_args__ = (
397 UniqueConstraint('ui_key'),
397 UniqueConstraint('ui_key'),
398 base_table_args
398 base_table_args
399 )
399 )
400
400
401 HOOK_REPO_SIZE = 'changegroup.repo_size'
401 HOOK_REPO_SIZE = 'changegroup.repo_size'
402 # HG
402 # HG
403 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
403 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
404 HOOK_PULL = 'outgoing.pull_logger'
404 HOOK_PULL = 'outgoing.pull_logger'
405 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
405 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
406 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
406 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
407 HOOK_PUSH = 'changegroup.push_logger'
407 HOOK_PUSH = 'changegroup.push_logger'
408 HOOK_PUSH_KEY = 'pushkey.key_push'
408 HOOK_PUSH_KEY = 'pushkey.key_push'
409
409
410 # TODO: johbo: Unify way how hooks are configured for git and hg,
410 # TODO: johbo: Unify way how hooks are configured for git and hg,
411 # git part is currently hardcoded.
411 # git part is currently hardcoded.
412
412
413 # SVN PATTERNS
413 # SVN PATTERNS
414 SVN_BRANCH_ID = 'vcs_svn_branch'
414 SVN_BRANCH_ID = 'vcs_svn_branch'
415 SVN_TAG_ID = 'vcs_svn_tag'
415 SVN_TAG_ID = 'vcs_svn_tag'
416
416
417 ui_id = Column(
417 ui_id = Column(
418 "ui_id", Integer(), nullable=False, unique=True, default=None,
418 "ui_id", Integer(), nullable=False, unique=True, default=None,
419 primary_key=True)
419 primary_key=True)
420 ui_section = Column(
420 ui_section = Column(
421 "ui_section", String(255), nullable=True, unique=None, default=None)
421 "ui_section", String(255), nullable=True, unique=None, default=None)
422 ui_key = Column(
422 ui_key = Column(
423 "ui_key", String(255), nullable=True, unique=None, default=None)
423 "ui_key", String(255), nullable=True, unique=None, default=None)
424 ui_value = Column(
424 ui_value = Column(
425 "ui_value", String(255), nullable=True, unique=None, default=None)
425 "ui_value", String(255), nullable=True, unique=None, default=None)
426 ui_active = Column(
426 ui_active = Column(
427 "ui_active", Boolean(), nullable=True, unique=None, default=True)
427 "ui_active", Boolean(), nullable=True, unique=None, default=True)
428
428
429 def __repr__(self):
429 def __repr__(self):
430 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
430 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
431 self.ui_key, self.ui_value)
431 self.ui_key, self.ui_value)
432
432
433
433
434 class RepoRhodeCodeSetting(Base, BaseModel):
434 class RepoRhodeCodeSetting(Base, BaseModel):
435 __tablename__ = 'repo_rhodecode_settings'
435 __tablename__ = 'repo_rhodecode_settings'
436 __table_args__ = (
436 __table_args__ = (
437 UniqueConstraint(
437 UniqueConstraint(
438 'app_settings_name', 'repository_id',
438 'app_settings_name', 'repository_id',
439 name='uq_repo_rhodecode_setting_name_repo_id'),
439 name='uq_repo_rhodecode_setting_name_repo_id'),
440 base_table_args
440 base_table_args
441 )
441 )
442
442
443 repository_id = Column(
443 repository_id = Column(
444 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
444 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
445 nullable=False)
445 nullable=False)
446 app_settings_id = Column(
446 app_settings_id = Column(
447 "app_settings_id", Integer(), nullable=False, unique=True,
447 "app_settings_id", Integer(), nullable=False, unique=True,
448 default=None, primary_key=True)
448 default=None, primary_key=True)
449 app_settings_name = Column(
449 app_settings_name = Column(
450 "app_settings_name", String(255), nullable=True, unique=None,
450 "app_settings_name", String(255), nullable=True, unique=None,
451 default=None)
451 default=None)
452 _app_settings_value = Column(
452 _app_settings_value = Column(
453 "app_settings_value", String(4096), nullable=True, unique=None,
453 "app_settings_value", String(4096), nullable=True, unique=None,
454 default=None)
454 default=None)
455 _app_settings_type = Column(
455 _app_settings_type = Column(
456 "app_settings_type", String(255), nullable=True, unique=None,
456 "app_settings_type", String(255), nullable=True, unique=None,
457 default=None)
457 default=None)
458
458
459 repository = relationship('Repository')
459 repository = relationship('Repository')
460
460
461 def __init__(self, repository_id, key='', val='', type='unicode'):
461 def __init__(self, repository_id, key='', val='', type='unicode'):
462 self.repository_id = repository_id
462 self.repository_id = repository_id
463 self.app_settings_name = key
463 self.app_settings_name = key
464 self.app_settings_type = type
464 self.app_settings_type = type
465 self.app_settings_value = val
465 self.app_settings_value = val
466
466
467 @validates('_app_settings_value')
467 @validates('_app_settings_value')
468 def validate_settings_value(self, key, val):
468 def validate_settings_value(self, key, val):
469 assert type(val) == unicode
469 assert type(val) == unicode
470 return val
470 return val
471
471
472 @hybrid_property
472 @hybrid_property
473 def app_settings_value(self):
473 def app_settings_value(self):
474 v = self._app_settings_value
474 v = self._app_settings_value
475 type_ = self.app_settings_type
475 type_ = self.app_settings_type
476 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
476 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
477 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
477 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
478 return converter(v)
478 return converter(v)
479
479
480 @app_settings_value.setter
480 @app_settings_value.setter
481 def app_settings_value(self, val):
481 def app_settings_value(self, val):
482 """
482 """
483 Setter that will always make sure we use unicode in app_settings_value
483 Setter that will always make sure we use unicode in app_settings_value
484
484
485 :param val:
485 :param val:
486 """
486 """
487 self._app_settings_value = safe_unicode(val)
487 self._app_settings_value = safe_unicode(val)
488
488
489 @hybrid_property
489 @hybrid_property
490 def app_settings_type(self):
490 def app_settings_type(self):
491 return self._app_settings_type
491 return self._app_settings_type
492
492
493 @app_settings_type.setter
493 @app_settings_type.setter
494 def app_settings_type(self, val):
494 def app_settings_type(self, val):
495 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
495 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
496 if val not in SETTINGS_TYPES:
496 if val not in SETTINGS_TYPES:
497 raise Exception('type must be one of %s got %s'
497 raise Exception('type must be one of %s got %s'
498 % (SETTINGS_TYPES.keys(), val))
498 % (SETTINGS_TYPES.keys(), val))
499 self._app_settings_type = val
499 self._app_settings_type = val
500
500
501 def __unicode__(self):
501 def __unicode__(self):
502 return u"<%s('%s:%s:%s[%s]')>" % (
502 return u"<%s('%s:%s:%s[%s]')>" % (
503 self.__class__.__name__, self.repository.repo_name,
503 self.__class__.__name__, self.repository.repo_name,
504 self.app_settings_name, self.app_settings_value,
504 self.app_settings_name, self.app_settings_value,
505 self.app_settings_type
505 self.app_settings_type
506 )
506 )
507
507
508
508
509 class RepoRhodeCodeUi(Base, BaseModel):
509 class RepoRhodeCodeUi(Base, BaseModel):
510 __tablename__ = 'repo_rhodecode_ui'
510 __tablename__ = 'repo_rhodecode_ui'
511 __table_args__ = (
511 __table_args__ = (
512 UniqueConstraint(
512 UniqueConstraint(
513 'repository_id', 'ui_section', 'ui_key',
513 'repository_id', 'ui_section', 'ui_key',
514 name='uq_repo_rhodecode_ui_repository_id_section_key'),
514 name='uq_repo_rhodecode_ui_repository_id_section_key'),
515 base_table_args
515 base_table_args
516 )
516 )
517
517
518 repository_id = Column(
518 repository_id = Column(
519 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
519 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
520 nullable=False)
520 nullable=False)
521 ui_id = Column(
521 ui_id = Column(
522 "ui_id", Integer(), nullable=False, unique=True, default=None,
522 "ui_id", Integer(), nullable=False, unique=True, default=None,
523 primary_key=True)
523 primary_key=True)
524 ui_section = Column(
524 ui_section = Column(
525 "ui_section", String(255), nullable=True, unique=None, default=None)
525 "ui_section", String(255), nullable=True, unique=None, default=None)
526 ui_key = Column(
526 ui_key = Column(
527 "ui_key", String(255), nullable=True, unique=None, default=None)
527 "ui_key", String(255), nullable=True, unique=None, default=None)
528 ui_value = Column(
528 ui_value = Column(
529 "ui_value", String(255), nullable=True, unique=None, default=None)
529 "ui_value", String(255), nullable=True, unique=None, default=None)
530 ui_active = Column(
530 ui_active = Column(
531 "ui_active", Boolean(), nullable=True, unique=None, default=True)
531 "ui_active", Boolean(), nullable=True, unique=None, default=True)
532
532
533 repository = relationship('Repository')
533 repository = relationship('Repository')
534
534
535 def __repr__(self):
535 def __repr__(self):
536 return '<%s[%s:%s]%s=>%s]>' % (
536 return '<%s[%s:%s]%s=>%s]>' % (
537 self.__class__.__name__, self.repository.repo_name,
537 self.__class__.__name__, self.repository.repo_name,
538 self.ui_section, self.ui_key, self.ui_value)
538 self.ui_section, self.ui_key, self.ui_value)
539
539
540
540
541 class User(Base, BaseModel):
541 class User(Base, BaseModel):
542 __tablename__ = 'users'
542 __tablename__ = 'users'
543 __table_args__ = (
543 __table_args__ = (
544 UniqueConstraint('username'), UniqueConstraint('email'),
544 UniqueConstraint('username'), UniqueConstraint('email'),
545 Index('u_username_idx', 'username'),
545 Index('u_username_idx', 'username'),
546 Index('u_email_idx', 'email'),
546 Index('u_email_idx', 'email'),
547 base_table_args
547 base_table_args
548 )
548 )
549
549
550 DEFAULT_USER = 'default'
550 DEFAULT_USER = 'default'
551 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
551 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
552 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
552 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
553
553
554 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
554 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
555 username = Column("username", String(255), nullable=True, unique=None, default=None)
555 username = Column("username", String(255), nullable=True, unique=None, default=None)
556 password = Column("password", String(255), nullable=True, unique=None, default=None)
556 password = Column("password", String(255), nullable=True, unique=None, default=None)
557 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
557 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
558 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
558 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
559 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
559 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
560 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
560 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
561 _email = Column("email", String(255), nullable=True, unique=None, default=None)
561 _email = Column("email", String(255), nullable=True, unique=None, default=None)
562 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
562 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
563 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
563 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
564
564
565 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
565 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
566 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
566 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
567 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
567 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
568 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
568 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
569 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
569 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
570 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
570 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
571
571
572 user_log = relationship('UserLog')
572 user_log = relationship('UserLog')
573 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
573 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
574
574
575 repositories = relationship('Repository')
575 repositories = relationship('Repository')
576 repository_groups = relationship('RepoGroup')
576 repository_groups = relationship('RepoGroup')
577 user_groups = relationship('UserGroup')
577 user_groups = relationship('UserGroup')
578
578
579 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
579 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
580 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
580 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
581
581
582 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
582 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
583 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
583 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
584 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
584 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
585
585
586 group_member = relationship('UserGroupMember', cascade='all')
586 group_member = relationship('UserGroupMember', cascade='all')
587
587
588 notifications = relationship('UserNotification', cascade='all')
588 notifications = relationship('UserNotification', cascade='all')
589 # notifications assigned to this user
589 # notifications assigned to this user
590 user_created_notifications = relationship('Notification', cascade='all')
590 user_created_notifications = relationship('Notification', cascade='all')
591 # comments created by this user
591 # comments created by this user
592 user_comments = relationship('ChangesetComment', cascade='all')
592 user_comments = relationship('ChangesetComment', cascade='all')
593 # user profile extra info
593 # user profile extra info
594 user_emails = relationship('UserEmailMap', cascade='all')
594 user_emails = relationship('UserEmailMap', cascade='all')
595 user_ip_map = relationship('UserIpMap', cascade='all')
595 user_ip_map = relationship('UserIpMap', cascade='all')
596 user_auth_tokens = relationship('UserApiKeys', cascade='all')
596 user_auth_tokens = relationship('UserApiKeys', cascade='all')
597 user_ssh_keys = relationship('UserSshKeys', cascade='all')
597 user_ssh_keys = relationship('UserSshKeys', cascade='all')
598
598
599 # gists
599 # gists
600 user_gists = relationship('Gist', cascade='all')
600 user_gists = relationship('Gist', cascade='all')
601 # user pull requests
601 # user pull requests
602 user_pull_requests = relationship('PullRequest', cascade='all')
602 user_pull_requests = relationship('PullRequest', cascade='all')
603 # external identities
603 # external identities
604 extenal_identities = relationship(
604 extenal_identities = relationship(
605 'ExternalIdentity',
605 'ExternalIdentity',
606 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
606 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
607 cascade='all')
607 cascade='all')
608 # review rules
608 # review rules
609 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
609 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
610
610
611 def __unicode__(self):
611 def __unicode__(self):
612 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
612 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
613 self.user_id, self.username)
613 self.user_id, self.username)
614
614
615 @hybrid_property
615 @hybrid_property
616 def email(self):
616 def email(self):
617 return self._email
617 return self._email
618
618
619 @email.setter
619 @email.setter
620 def email(self, val):
620 def email(self, val):
621 self._email = val.lower() if val else None
621 self._email = val.lower() if val else None
622
622
623 @hybrid_property
623 @hybrid_property
624 def first_name(self):
624 def first_name(self):
625 from rhodecode.lib import helpers as h
625 from rhodecode.lib import helpers as h
626 if self.name:
626 if self.name:
627 return h.escape(self.name)
627 return h.escape(self.name)
628 return self.name
628 return self.name
629
629
630 @hybrid_property
630 @hybrid_property
631 def last_name(self):
631 def last_name(self):
632 from rhodecode.lib import helpers as h
632 from rhodecode.lib import helpers as h
633 if self.lastname:
633 if self.lastname:
634 return h.escape(self.lastname)
634 return h.escape(self.lastname)
635 return self.lastname
635 return self.lastname
636
636
637 @hybrid_property
637 @hybrid_property
638 def api_key(self):
638 def api_key(self):
639 """
639 """
640 Fetch if exist an auth-token with role ALL connected to this user
640 Fetch if exist an auth-token with role ALL connected to this user
641 """
641 """
642 user_auth_token = UserApiKeys.query()\
642 user_auth_token = UserApiKeys.query()\
643 .filter(UserApiKeys.user_id == self.user_id)\
643 .filter(UserApiKeys.user_id == self.user_id)\
644 .filter(or_(UserApiKeys.expires == -1,
644 .filter(or_(UserApiKeys.expires == -1,
645 UserApiKeys.expires >= time.time()))\
645 UserApiKeys.expires >= time.time()))\
646 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
646 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
647 if user_auth_token:
647 if user_auth_token:
648 user_auth_token = user_auth_token.api_key
648 user_auth_token = user_auth_token.api_key
649
649
650 return user_auth_token
650 return user_auth_token
651
651
652 @api_key.setter
652 @api_key.setter
653 def api_key(self, val):
653 def api_key(self, val):
654 # don't allow to set API key this is deprecated for now
654 # don't allow to set API key this is deprecated for now
655 self._api_key = None
655 self._api_key = None
656
656
657 @property
657 @property
658 def reviewer_pull_requests(self):
658 def reviewer_pull_requests(self):
659 return PullRequestReviewers.query() \
659 return PullRequestReviewers.query() \
660 .options(joinedload(PullRequestReviewers.pull_request)) \
660 .options(joinedload(PullRequestReviewers.pull_request)) \
661 .filter(PullRequestReviewers.user_id == self.user_id) \
661 .filter(PullRequestReviewers.user_id == self.user_id) \
662 .all()
662 .all()
663
663
664 @property
664 @property
665 def firstname(self):
665 def firstname(self):
666 # alias for future
666 # alias for future
667 return self.name
667 return self.name
668
668
669 @property
669 @property
670 def emails(self):
670 def emails(self):
671 other = UserEmailMap.query()\
671 other = UserEmailMap.query()\
672 .filter(UserEmailMap.user == self) \
672 .filter(UserEmailMap.user == self) \
673 .order_by(UserEmailMap.email_id.asc()) \
673 .order_by(UserEmailMap.email_id.asc()) \
674 .all()
674 .all()
675 return [self.email] + [x.email for x in other]
675 return [self.email] + [x.email for x in other]
676
676
677 @property
677 @property
678 def auth_tokens(self):
678 def auth_tokens(self):
679 auth_tokens = self.get_auth_tokens()
679 auth_tokens = self.get_auth_tokens()
680 return [x.api_key for x in auth_tokens]
680 return [x.api_key for x in auth_tokens]
681
681
682 def get_auth_tokens(self):
682 def get_auth_tokens(self):
683 return UserApiKeys.query()\
683 return UserApiKeys.query()\
684 .filter(UserApiKeys.user == self)\
684 .filter(UserApiKeys.user == self)\
685 .order_by(UserApiKeys.user_api_key_id.asc())\
685 .order_by(UserApiKeys.user_api_key_id.asc())\
686 .all()
686 .all()
687
687
688 @LazyProperty
688 @LazyProperty
689 def feed_token(self):
689 def feed_token(self):
690 return self.get_feed_token()
690 return self.get_feed_token()
691
691
692 def get_feed_token(self, cache=True):
692 def get_feed_token(self, cache=True):
693 feed_tokens = UserApiKeys.query()\
693 feed_tokens = UserApiKeys.query()\
694 .filter(UserApiKeys.user == self)\
694 .filter(UserApiKeys.user == self)\
695 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
695 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
696 if cache:
696 if cache:
697 feed_tokens = feed_tokens.options(
697 feed_tokens = feed_tokens.options(
698 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
698 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
699
699
700 feed_tokens = feed_tokens.all()
700 feed_tokens = feed_tokens.all()
701 if feed_tokens:
701 if feed_tokens:
702 return feed_tokens[0].api_key
702 return feed_tokens[0].api_key
703 return 'NO_FEED_TOKEN_AVAILABLE'
703 return 'NO_FEED_TOKEN_AVAILABLE'
704
704
705 @classmethod
705 @classmethod
706 def get(cls, user_id, cache=False):
706 def get(cls, user_id, cache=False):
707 if not user_id:
707 if not user_id:
708 return
708 return
709
709
710 user = cls.query()
710 user = cls.query()
711 if cache:
711 if cache:
712 user = user.options(
712 user = user.options(
713 FromCache("sql_cache_short", "get_users_%s" % user_id))
713 FromCache("sql_cache_short", "get_users_%s" % user_id))
714 return user.get(user_id)
714 return user.get(user_id)
715
715
716 @classmethod
716 @classmethod
717 def extra_valid_auth_tokens(cls, user, role=None):
717 def extra_valid_auth_tokens(cls, user, role=None):
718 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
718 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
719 .filter(or_(UserApiKeys.expires == -1,
719 .filter(or_(UserApiKeys.expires == -1,
720 UserApiKeys.expires >= time.time()))
720 UserApiKeys.expires >= time.time()))
721 if role:
721 if role:
722 tokens = tokens.filter(or_(UserApiKeys.role == role,
722 tokens = tokens.filter(or_(UserApiKeys.role == role,
723 UserApiKeys.role == UserApiKeys.ROLE_ALL))
723 UserApiKeys.role == UserApiKeys.ROLE_ALL))
724 return tokens.all()
724 return tokens.all()
725
725
726 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
726 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
727 from rhodecode.lib import auth
727 from rhodecode.lib import auth
728
728
729 log.debug('Trying to authenticate user: %s via auth-token, '
729 log.debug('Trying to authenticate user: %s via auth-token, '
730 'and roles: %s', self, roles)
730 'and roles: %s', self, roles)
731
731
732 if not auth_token:
732 if not auth_token:
733 return False
733 return False
734
734
735 crypto_backend = auth.crypto_backend()
735 crypto_backend = auth.crypto_backend()
736
736
737 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
737 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
738 tokens_q = UserApiKeys.query()\
738 tokens_q = UserApiKeys.query()\
739 .filter(UserApiKeys.user_id == self.user_id)\
739 .filter(UserApiKeys.user_id == self.user_id)\
740 .filter(or_(UserApiKeys.expires == -1,
740 .filter(or_(UserApiKeys.expires == -1,
741 UserApiKeys.expires >= time.time()))
741 UserApiKeys.expires >= time.time()))
742
742
743 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
743 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
744
744
745 plain_tokens = []
745 plain_tokens = []
746 hash_tokens = []
746 hash_tokens = []
747
747
748 user_tokens = tokens_q.all()
748 user_tokens = tokens_q.all()
749 log.debug('Found %s user tokens to check for authentication', len(user_tokens))
749 log.debug('Found %s user tokens to check for authentication', len(user_tokens))
750 for token in user_tokens:
750 for token in user_tokens:
751 log.debug('AUTH_TOKEN: checking if user token with id `%s` matches',
751 log.debug('AUTH_TOKEN: checking if user token with id `%s` matches',
752 token.user_api_key_id)
752 token.user_api_key_id)
753 # verify scope first, since it's way faster than hash calculation of
753 # verify scope first, since it's way faster than hash calculation of
754 # encrypted tokens
754 # encrypted tokens
755 if token.repo_id:
755 if token.repo_id:
756 # token has a scope, we need to verify it
756 # token has a scope, we need to verify it
757 if scope_repo_id != token.repo_id:
757 if scope_repo_id != token.repo_id:
758 log.debug(
758 log.debug(
759 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
759 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
760 'and calling scope is:%s, skipping further checks',
760 'and calling scope is:%s, skipping further checks',
761 token.repo, scope_repo_id)
761 token.repo, scope_repo_id)
762 # token has a scope, and it doesn't match, skip token
762 # token has a scope, and it doesn't match, skip token
763 continue
763 continue
764
764
765 if token.api_key.startswith(crypto_backend.ENC_PREF):
765 if token.api_key.startswith(crypto_backend.ENC_PREF):
766 hash_tokens.append(token.api_key)
766 hash_tokens.append(token.api_key)
767 else:
767 else:
768 plain_tokens.append(token.api_key)
768 plain_tokens.append(token.api_key)
769
769
770 is_plain_match = auth_token in plain_tokens
770 is_plain_match = auth_token in plain_tokens
771 if is_plain_match:
771 if is_plain_match:
772 return True
772 return True
773
773
774 for hashed in hash_tokens:
774 for hashed in hash_tokens:
775 # NOTE(marcink): this is expensive to calculate, but most secure
775 # NOTE(marcink): this is expensive to calculate, but most secure
776 match = crypto_backend.hash_check(auth_token, hashed)
776 match = crypto_backend.hash_check(auth_token, hashed)
777 if match:
777 if match:
778 return True
778 return True
779
779
780 return False
780 return False
781
781
782 @property
782 @property
783 def ip_addresses(self):
783 def ip_addresses(self):
784 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
784 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
785 return [x.ip_addr for x in ret]
785 return [x.ip_addr for x in ret]
786
786
787 @property
787 @property
788 def username_and_name(self):
788 def username_and_name(self):
789 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
789 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
790
790
791 @property
791 @property
792 def username_or_name_or_email(self):
792 def username_or_name_or_email(self):
793 full_name = self.full_name if self.full_name is not ' ' else None
793 full_name = self.full_name if self.full_name is not ' ' else None
794 return self.username or full_name or self.email
794 return self.username or full_name or self.email
795
795
796 @property
796 @property
797 def full_name(self):
797 def full_name(self):
798 return '%s %s' % (self.first_name, self.last_name)
798 return '%s %s' % (self.first_name, self.last_name)
799
799
800 @property
800 @property
801 def full_name_or_username(self):
801 def full_name_or_username(self):
802 return ('%s %s' % (self.first_name, self.last_name)
802 return ('%s %s' % (self.first_name, self.last_name)
803 if (self.first_name and self.last_name) else self.username)
803 if (self.first_name and self.last_name) else self.username)
804
804
805 @property
805 @property
806 def full_contact(self):
806 def full_contact(self):
807 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
807 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
808
808
809 @property
809 @property
810 def short_contact(self):
810 def short_contact(self):
811 return '%s %s' % (self.first_name, self.last_name)
811 return '%s %s' % (self.first_name, self.last_name)
812
812
813 @property
813 @property
814 def is_admin(self):
814 def is_admin(self):
815 return self.admin
815 return self.admin
816
816
817 def AuthUser(self, **kwargs):
817 def AuthUser(self, **kwargs):
818 """
818 """
819 Returns instance of AuthUser for this user
819 Returns instance of AuthUser for this user
820 """
820 """
821 from rhodecode.lib.auth import AuthUser
821 from rhodecode.lib.auth import AuthUser
822 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
822 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
823
823
824 @hybrid_property
824 @hybrid_property
825 def user_data(self):
825 def user_data(self):
826 if not self._user_data:
826 if not self._user_data:
827 return {}
827 return {}
828
828
829 try:
829 try:
830 return json.loads(self._user_data)
830 return json.loads(self._user_data)
831 except TypeError:
831 except TypeError:
832 return {}
832 return {}
833
833
834 @user_data.setter
834 @user_data.setter
835 def user_data(self, val):
835 def user_data(self, val):
836 if not isinstance(val, dict):
836 if not isinstance(val, dict):
837 raise Exception('user_data must be dict, got %s' % type(val))
837 raise Exception('user_data must be dict, got %s' % type(val))
838 try:
838 try:
839 self._user_data = json.dumps(val)
839 self._user_data = json.dumps(val)
840 except Exception:
840 except Exception:
841 log.error(traceback.format_exc())
841 log.error(traceback.format_exc())
842
842
843 @classmethod
843 @classmethod
844 def get_by_username(cls, username, case_insensitive=False,
844 def get_by_username(cls, username, case_insensitive=False,
845 cache=False, identity_cache=False):
845 cache=False, identity_cache=False):
846 session = Session()
846 session = Session()
847
847
848 if case_insensitive:
848 if case_insensitive:
849 q = cls.query().filter(
849 q = cls.query().filter(
850 func.lower(cls.username) == func.lower(username))
850 func.lower(cls.username) == func.lower(username))
851 else:
851 else:
852 q = cls.query().filter(cls.username == username)
852 q = cls.query().filter(cls.username == username)
853
853
854 if cache:
854 if cache:
855 if identity_cache:
855 if identity_cache:
856 val = cls.identity_cache(session, 'username', username)
856 val = cls.identity_cache(session, 'username', username)
857 if val:
857 if val:
858 return val
858 return val
859 else:
859 else:
860 cache_key = "get_user_by_name_%s" % _hash_key(username)
860 cache_key = "get_user_by_name_%s" % _hash_key(username)
861 q = q.options(
861 q = q.options(
862 FromCache("sql_cache_short", cache_key))
862 FromCache("sql_cache_short", cache_key))
863
863
864 return q.scalar()
864 return q.scalar()
865
865
866 @classmethod
866 @classmethod
867 def get_by_auth_token(cls, auth_token, cache=False):
867 def get_by_auth_token(cls, auth_token, cache=False):
868 q = UserApiKeys.query()\
868 q = UserApiKeys.query()\
869 .filter(UserApiKeys.api_key == auth_token)\
869 .filter(UserApiKeys.api_key == auth_token)\
870 .filter(or_(UserApiKeys.expires == -1,
870 .filter(or_(UserApiKeys.expires == -1,
871 UserApiKeys.expires >= time.time()))
871 UserApiKeys.expires >= time.time()))
872 if cache:
872 if cache:
873 q = q.options(
873 q = q.options(
874 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
874 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
875
875
876 match = q.first()
876 match = q.first()
877 if match:
877 if match:
878 return match.user
878 return match.user
879
879
880 @classmethod
880 @classmethod
881 def get_by_email(cls, email, case_insensitive=False, cache=False):
881 def get_by_email(cls, email, case_insensitive=False, cache=False):
882
882
883 if case_insensitive:
883 if case_insensitive:
884 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
884 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
885
885
886 else:
886 else:
887 q = cls.query().filter(cls.email == email)
887 q = cls.query().filter(cls.email == email)
888
888
889 email_key = _hash_key(email)
889 email_key = _hash_key(email)
890 if cache:
890 if cache:
891 q = q.options(
891 q = q.options(
892 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
892 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
893
893
894 ret = q.scalar()
894 ret = q.scalar()
895 if ret is None:
895 if ret is None:
896 q = UserEmailMap.query()
896 q = UserEmailMap.query()
897 # try fetching in alternate email map
897 # try fetching in alternate email map
898 if case_insensitive:
898 if case_insensitive:
899 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
899 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
900 else:
900 else:
901 q = q.filter(UserEmailMap.email == email)
901 q = q.filter(UserEmailMap.email == email)
902 q = q.options(joinedload(UserEmailMap.user))
902 q = q.options(joinedload(UserEmailMap.user))
903 if cache:
903 if cache:
904 q = q.options(
904 q = q.options(
905 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
905 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
906 ret = getattr(q.scalar(), 'user', None)
906 ret = getattr(q.scalar(), 'user', None)
907
907
908 return ret
908 return ret
909
909
910 @classmethod
910 @classmethod
911 def get_from_cs_author(cls, author):
911 def get_from_cs_author(cls, author):
912 """
912 """
913 Tries to get User objects out of commit author string
913 Tries to get User objects out of commit author string
914
914
915 :param author:
915 :param author:
916 """
916 """
917 from rhodecode.lib.helpers import email, author_name
917 from rhodecode.lib.helpers import email, author_name
918 # Valid email in the attribute passed, see if they're in the system
918 # Valid email in the attribute passed, see if they're in the system
919 _email = email(author)
919 _email = email(author)
920 if _email:
920 if _email:
921 user = cls.get_by_email(_email, case_insensitive=True)
921 user = cls.get_by_email(_email, case_insensitive=True)
922 if user:
922 if user:
923 return user
923 return user
924 # Maybe we can match by username?
924 # Maybe we can match by username?
925 _author = author_name(author)
925 _author = author_name(author)
926 user = cls.get_by_username(_author, case_insensitive=True)
926 user = cls.get_by_username(_author, case_insensitive=True)
927 if user:
927 if user:
928 return user
928 return user
929
929
930 def update_userdata(self, **kwargs):
930 def update_userdata(self, **kwargs):
931 usr = self
931 usr = self
932 old = usr.user_data
932 old = usr.user_data
933 old.update(**kwargs)
933 old.update(**kwargs)
934 usr.user_data = old
934 usr.user_data = old
935 Session().add(usr)
935 Session().add(usr)
936 log.debug('updated userdata with ', kwargs)
936 log.debug('updated userdata with ', kwargs)
937
937
938 def update_lastlogin(self):
938 def update_lastlogin(self):
939 """Update user lastlogin"""
939 """Update user lastlogin"""
940 self.last_login = datetime.datetime.now()
940 self.last_login = datetime.datetime.now()
941 Session().add(self)
941 Session().add(self)
942 log.debug('updated user %s lastlogin', self.username)
942 log.debug('updated user %s lastlogin', self.username)
943
943
944 def update_password(self, new_password):
944 def update_password(self, new_password):
945 from rhodecode.lib.auth import get_crypt_password
945 from rhodecode.lib.auth import get_crypt_password
946
946
947 self.password = get_crypt_password(new_password)
947 self.password = get_crypt_password(new_password)
948 Session().add(self)
948 Session().add(self)
949
949
950 @classmethod
950 @classmethod
951 def get_first_super_admin(cls):
951 def get_first_super_admin(cls):
952 user = User.query()\
952 user = User.query()\
953 .filter(User.admin == true()) \
953 .filter(User.admin == true()) \
954 .order_by(User.user_id.asc()) \
954 .order_by(User.user_id.asc()) \
955 .first()
955 .first()
956
956
957 if user is None:
957 if user is None:
958 raise Exception('FATAL: Missing administrative account!')
958 raise Exception('FATAL: Missing administrative account!')
959 return user
959 return user
960
960
961 @classmethod
961 @classmethod
962 def get_all_super_admins(cls):
962 def get_all_super_admins(cls):
963 """
963 """
964 Returns all admin accounts sorted by username
964 Returns all admin accounts sorted by username
965 """
965 """
966 return User.query().filter(User.admin == true())\
966 return User.query().filter(User.admin == true())\
967 .order_by(User.username.asc()).all()
967 .order_by(User.username.asc()).all()
968
968
969 @classmethod
969 @classmethod
970 def get_default_user(cls, cache=False, refresh=False):
970 def get_default_user(cls, cache=False, refresh=False):
971 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
971 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
972 if user is None:
972 if user is None:
973 raise Exception('FATAL: Missing default account!')
973 raise Exception('FATAL: Missing default account!')
974 if refresh:
974 if refresh:
975 # The default user might be based on outdated state which
975 # The default user might be based on outdated state which
976 # has been loaded from the cache.
976 # has been loaded from the cache.
977 # A call to refresh() ensures that the
977 # A call to refresh() ensures that the
978 # latest state from the database is used.
978 # latest state from the database is used.
979 Session().refresh(user)
979 Session().refresh(user)
980 return user
980 return user
981
981
982 def _get_default_perms(self, user, suffix=''):
982 def _get_default_perms(self, user, suffix=''):
983 from rhodecode.model.permission import PermissionModel
983 from rhodecode.model.permission import PermissionModel
984 return PermissionModel().get_default_perms(user.user_perms, suffix)
984 return PermissionModel().get_default_perms(user.user_perms, suffix)
985
985
986 def get_default_perms(self, suffix=''):
986 def get_default_perms(self, suffix=''):
987 return self._get_default_perms(self, suffix)
987 return self._get_default_perms(self, suffix)
988
988
989 def get_api_data(self, include_secrets=False, details='full'):
989 def get_api_data(self, include_secrets=False, details='full'):
990 """
990 """
991 Common function for generating user related data for API
991 Common function for generating user related data for API
992
992
993 :param include_secrets: By default secrets in the API data will be replaced
993 :param include_secrets: By default secrets in the API data will be replaced
994 by a placeholder value to prevent exposing this data by accident. In case
994 by a placeholder value to prevent exposing this data by accident. In case
995 this data shall be exposed, set this flag to ``True``.
995 this data shall be exposed, set this flag to ``True``.
996
996
997 :param details: details can be 'basic|full' basic gives only a subset of
997 :param details: details can be 'basic|full' basic gives only a subset of
998 the available user information that includes user_id, name and emails.
998 the available user information that includes user_id, name and emails.
999 """
999 """
1000 user = self
1000 user = self
1001 user_data = self.user_data
1001 user_data = self.user_data
1002 data = {
1002 data = {
1003 'user_id': user.user_id,
1003 'user_id': user.user_id,
1004 'username': user.username,
1004 'username': user.username,
1005 'firstname': user.name,
1005 'firstname': user.name,
1006 'lastname': user.lastname,
1006 'lastname': user.lastname,
1007 'email': user.email,
1007 'email': user.email,
1008 'emails': user.emails,
1008 'emails': user.emails,
1009 }
1009 }
1010 if details == 'basic':
1010 if details == 'basic':
1011 return data
1011 return data
1012
1012
1013 auth_token_length = 40
1013 auth_token_length = 40
1014 auth_token_replacement = '*' * auth_token_length
1014 auth_token_replacement = '*' * auth_token_length
1015
1015
1016 extras = {
1016 extras = {
1017 'auth_tokens': [auth_token_replacement],
1017 'auth_tokens': [auth_token_replacement],
1018 'active': user.active,
1018 'active': user.active,
1019 'admin': user.admin,
1019 'admin': user.admin,
1020 'extern_type': user.extern_type,
1020 'extern_type': user.extern_type,
1021 'extern_name': user.extern_name,
1021 'extern_name': user.extern_name,
1022 'last_login': user.last_login,
1022 'last_login': user.last_login,
1023 'last_activity': user.last_activity,
1023 'last_activity': user.last_activity,
1024 'ip_addresses': user.ip_addresses,
1024 'ip_addresses': user.ip_addresses,
1025 'language': user_data.get('language')
1025 'language': user_data.get('language')
1026 }
1026 }
1027 data.update(extras)
1027 data.update(extras)
1028
1028
1029 if include_secrets:
1029 if include_secrets:
1030 data['auth_tokens'] = user.auth_tokens
1030 data['auth_tokens'] = user.auth_tokens
1031 return data
1031 return data
1032
1032
1033 def __json__(self):
1033 def __json__(self):
1034 data = {
1034 data = {
1035 'full_name': self.full_name,
1035 'full_name': self.full_name,
1036 'full_name_or_username': self.full_name_or_username,
1036 'full_name_or_username': self.full_name_or_username,
1037 'short_contact': self.short_contact,
1037 'short_contact': self.short_contact,
1038 'full_contact': self.full_contact,
1038 'full_contact': self.full_contact,
1039 }
1039 }
1040 data.update(self.get_api_data())
1040 data.update(self.get_api_data())
1041 return data
1041 return data
1042
1042
1043
1043
1044 class UserApiKeys(Base, BaseModel):
1044 class UserApiKeys(Base, BaseModel):
1045 __tablename__ = 'user_api_keys'
1045 __tablename__ = 'user_api_keys'
1046 __table_args__ = (
1046 __table_args__ = (
1047 Index('uak_api_key_idx', 'api_key', unique=True),
1047 Index('uak_api_key_idx', 'api_key', unique=True),
1048 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1048 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1049 base_table_args
1049 base_table_args
1050 )
1050 )
1051 __mapper_args__ = {}
1051 __mapper_args__ = {}
1052
1052
1053 # ApiKey role
1053 # ApiKey role
1054 ROLE_ALL = 'token_role_all'
1054 ROLE_ALL = 'token_role_all'
1055 ROLE_HTTP = 'token_role_http'
1055 ROLE_HTTP = 'token_role_http'
1056 ROLE_VCS = 'token_role_vcs'
1056 ROLE_VCS = 'token_role_vcs'
1057 ROLE_API = 'token_role_api'
1057 ROLE_API = 'token_role_api'
1058 ROLE_FEED = 'token_role_feed'
1058 ROLE_FEED = 'token_role_feed'
1059 ROLE_PASSWORD_RESET = 'token_password_reset'
1059 ROLE_PASSWORD_RESET = 'token_password_reset'
1060
1060
1061 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1061 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1062
1062
1063 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1063 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1065 api_key = Column("api_key", String(255), nullable=False, unique=True)
1065 api_key = Column("api_key", String(255), nullable=False, unique=True)
1066 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1066 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1067 expires = Column('expires', Float(53), nullable=False)
1067 expires = Column('expires', Float(53), nullable=False)
1068 role = Column('role', String(255), nullable=True)
1068 role = Column('role', String(255), nullable=True)
1069 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1069 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1070
1070
1071 # scope columns
1071 # scope columns
1072 repo_id = Column(
1072 repo_id = Column(
1073 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1073 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1074 nullable=True, unique=None, default=None)
1074 nullable=True, unique=None, default=None)
1075 repo = relationship('Repository', lazy='joined')
1075 repo = relationship('Repository', lazy='joined')
1076
1076
1077 repo_group_id = Column(
1077 repo_group_id = Column(
1078 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1078 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1079 nullable=True, unique=None, default=None)
1079 nullable=True, unique=None, default=None)
1080 repo_group = relationship('RepoGroup', lazy='joined')
1080 repo_group = relationship('RepoGroup', lazy='joined')
1081
1081
1082 user = relationship('User', lazy='joined')
1082 user = relationship('User', lazy='joined')
1083
1083
1084 def __unicode__(self):
1084 def __unicode__(self):
1085 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1085 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1086
1086
1087 def __json__(self):
1087 def __json__(self):
1088 data = {
1088 data = {
1089 'auth_token': self.api_key,
1089 'auth_token': self.api_key,
1090 'role': self.role,
1090 'role': self.role,
1091 'scope': self.scope_humanized,
1091 'scope': self.scope_humanized,
1092 'expired': self.expired
1092 'expired': self.expired
1093 }
1093 }
1094 return data
1094 return data
1095
1095
1096 def get_api_data(self, include_secrets=False):
1096 def get_api_data(self, include_secrets=False):
1097 data = self.__json__()
1097 data = self.__json__()
1098 if include_secrets:
1098 if include_secrets:
1099 return data
1099 return data
1100 else:
1100 else:
1101 data['auth_token'] = self.token_obfuscated
1101 data['auth_token'] = self.token_obfuscated
1102 return data
1102 return data
1103
1103
1104 @hybrid_property
1104 @hybrid_property
1105 def description_safe(self):
1105 def description_safe(self):
1106 from rhodecode.lib import helpers as h
1106 from rhodecode.lib import helpers as h
1107 return h.escape(self.description)
1107 return h.escape(self.description)
1108
1108
1109 @property
1109 @property
1110 def expired(self):
1110 def expired(self):
1111 if self.expires == -1:
1111 if self.expires == -1:
1112 return False
1112 return False
1113 return time.time() > self.expires
1113 return time.time() > self.expires
1114
1114
1115 @classmethod
1115 @classmethod
1116 def _get_role_name(cls, role):
1116 def _get_role_name(cls, role):
1117 return {
1117 return {
1118 cls.ROLE_ALL: _('all'),
1118 cls.ROLE_ALL: _('all'),
1119 cls.ROLE_HTTP: _('http/web interface'),
1119 cls.ROLE_HTTP: _('http/web interface'),
1120 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1120 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1121 cls.ROLE_API: _('api calls'),
1121 cls.ROLE_API: _('api calls'),
1122 cls.ROLE_FEED: _('feed access'),
1122 cls.ROLE_FEED: _('feed access'),
1123 }.get(role, role)
1123 }.get(role, role)
1124
1124
1125 @property
1125 @property
1126 def role_humanized(self):
1126 def role_humanized(self):
1127 return self._get_role_name(self.role)
1127 return self._get_role_name(self.role)
1128
1128
1129 def _get_scope(self):
1129 def _get_scope(self):
1130 if self.repo:
1130 if self.repo:
1131 return repr(self.repo)
1131 return repr(self.repo)
1132 if self.repo_group:
1132 if self.repo_group:
1133 return repr(self.repo_group) + ' (recursive)'
1133 return repr(self.repo_group) + ' (recursive)'
1134 return 'global'
1134 return 'global'
1135
1135
1136 @property
1136 @property
1137 def scope_humanized(self):
1137 def scope_humanized(self):
1138 return self._get_scope()
1138 return self._get_scope()
1139
1139
1140 @property
1140 @property
1141 def token_obfuscated(self):
1141 def token_obfuscated(self):
1142 if self.api_key:
1142 if self.api_key:
1143 return self.api_key[:4] + "****"
1143 return self.api_key[:4] + "****"
1144
1144
1145
1145
1146 class UserEmailMap(Base, BaseModel):
1146 class UserEmailMap(Base, BaseModel):
1147 __tablename__ = 'user_email_map'
1147 __tablename__ = 'user_email_map'
1148 __table_args__ = (
1148 __table_args__ = (
1149 Index('uem_email_idx', 'email'),
1149 Index('uem_email_idx', 'email'),
1150 UniqueConstraint('email'),
1150 UniqueConstraint('email'),
1151 base_table_args
1151 base_table_args
1152 )
1152 )
1153 __mapper_args__ = {}
1153 __mapper_args__ = {}
1154
1154
1155 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1155 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1156 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1156 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1157 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1157 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1158 user = relationship('User', lazy='joined')
1158 user = relationship('User', lazy='joined')
1159
1159
1160 @validates('_email')
1160 @validates('_email')
1161 def validate_email(self, key, email):
1161 def validate_email(self, key, email):
1162 # check if this email is not main one
1162 # check if this email is not main one
1163 main_email = Session().query(User).filter(User.email == email).scalar()
1163 main_email = Session().query(User).filter(User.email == email).scalar()
1164 if main_email is not None:
1164 if main_email is not None:
1165 raise AttributeError('email %s is present is user table' % email)
1165 raise AttributeError('email %s is present is user table' % email)
1166 return email
1166 return email
1167
1167
1168 @hybrid_property
1168 @hybrid_property
1169 def email(self):
1169 def email(self):
1170 return self._email
1170 return self._email
1171
1171
1172 @email.setter
1172 @email.setter
1173 def email(self, val):
1173 def email(self, val):
1174 self._email = val.lower() if val else None
1174 self._email = val.lower() if val else None
1175
1175
1176
1176
1177 class UserIpMap(Base, BaseModel):
1177 class UserIpMap(Base, BaseModel):
1178 __tablename__ = 'user_ip_map'
1178 __tablename__ = 'user_ip_map'
1179 __table_args__ = (
1179 __table_args__ = (
1180 UniqueConstraint('user_id', 'ip_addr'),
1180 UniqueConstraint('user_id', 'ip_addr'),
1181 base_table_args
1181 base_table_args
1182 )
1182 )
1183 __mapper_args__ = {}
1183 __mapper_args__ = {}
1184
1184
1185 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1185 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1186 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1186 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1187 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1187 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1188 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1188 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1189 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1189 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1190 user = relationship('User', lazy='joined')
1190 user = relationship('User', lazy='joined')
1191
1191
1192 @hybrid_property
1192 @hybrid_property
1193 def description_safe(self):
1193 def description_safe(self):
1194 from rhodecode.lib import helpers as h
1194 from rhodecode.lib import helpers as h
1195 return h.escape(self.description)
1195 return h.escape(self.description)
1196
1196
1197 @classmethod
1197 @classmethod
1198 def _get_ip_range(cls, ip_addr):
1198 def _get_ip_range(cls, ip_addr):
1199 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1199 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1200 return [str(net.network_address), str(net.broadcast_address)]
1200 return [str(net.network_address), str(net.broadcast_address)]
1201
1201
1202 def __json__(self):
1202 def __json__(self):
1203 return {
1203 return {
1204 'ip_addr': self.ip_addr,
1204 'ip_addr': self.ip_addr,
1205 'ip_range': self._get_ip_range(self.ip_addr),
1205 'ip_range': self._get_ip_range(self.ip_addr),
1206 }
1206 }
1207
1207
1208 def __unicode__(self):
1208 def __unicode__(self):
1209 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1209 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1210 self.user_id, self.ip_addr)
1210 self.user_id, self.ip_addr)
1211
1211
1212
1212
1213 class UserSshKeys(Base, BaseModel):
1213 class UserSshKeys(Base, BaseModel):
1214 __tablename__ = 'user_ssh_keys'
1214 __tablename__ = 'user_ssh_keys'
1215 __table_args__ = (
1215 __table_args__ = (
1216 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1216 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1217
1217
1218 UniqueConstraint('ssh_key_fingerprint'),
1218 UniqueConstraint('ssh_key_fingerprint'),
1219
1219
1220 base_table_args
1220 base_table_args
1221 )
1221 )
1222 __mapper_args__ = {}
1222 __mapper_args__ = {}
1223
1223
1224 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1225 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1225 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1226 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1226 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1227
1227
1228 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1228 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1229
1229
1230 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1230 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1231 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1231 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1232 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1232 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1233
1233
1234 user = relationship('User', lazy='joined')
1234 user = relationship('User', lazy='joined')
1235
1235
1236 def __json__(self):
1236 def __json__(self):
1237 data = {
1237 data = {
1238 'ssh_fingerprint': self.ssh_key_fingerprint,
1238 'ssh_fingerprint': self.ssh_key_fingerprint,
1239 'description': self.description,
1239 'description': self.description,
1240 'created_on': self.created_on
1240 'created_on': self.created_on
1241 }
1241 }
1242 return data
1242 return data
1243
1243
1244 def get_api_data(self):
1244 def get_api_data(self):
1245 data = self.__json__()
1245 data = self.__json__()
1246 return data
1246 return data
1247
1247
1248
1248
1249 class UserLog(Base, BaseModel):
1249 class UserLog(Base, BaseModel):
1250 __tablename__ = 'user_logs'
1250 __tablename__ = 'user_logs'
1251 __table_args__ = (
1251 __table_args__ = (
1252 base_table_args,
1252 base_table_args,
1253 )
1253 )
1254
1254
1255 VERSION_1 = 'v1'
1255 VERSION_1 = 'v1'
1256 VERSION_2 = 'v2'
1256 VERSION_2 = 'v2'
1257 VERSIONS = [VERSION_1, VERSION_2]
1257 VERSIONS = [VERSION_1, VERSION_2]
1258
1258
1259 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1259 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1260 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1260 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1261 username = Column("username", String(255), nullable=True, unique=None, default=None)
1261 username = Column("username", String(255), nullable=True, unique=None, default=None)
1262 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1262 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1263 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1263 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1264 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1264 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1265 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1265 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1266 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1266 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1267
1267
1268 version = Column("version", String(255), nullable=True, default=VERSION_1)
1268 version = Column("version", String(255), nullable=True, default=VERSION_1)
1269 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1269 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1270 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1270 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1271
1271
1272 def __unicode__(self):
1272 def __unicode__(self):
1273 return u"<%s('id:%s:%s')>" % (
1273 return u"<%s('id:%s:%s')>" % (
1274 self.__class__.__name__, self.repository_name, self.action)
1274 self.__class__.__name__, self.repository_name, self.action)
1275
1275
1276 def __json__(self):
1276 def __json__(self):
1277 return {
1277 return {
1278 'user_id': self.user_id,
1278 'user_id': self.user_id,
1279 'username': self.username,
1279 'username': self.username,
1280 'repository_id': self.repository_id,
1280 'repository_id': self.repository_id,
1281 'repository_name': self.repository_name,
1281 'repository_name': self.repository_name,
1282 'user_ip': self.user_ip,
1282 'user_ip': self.user_ip,
1283 'action_date': self.action_date,
1283 'action_date': self.action_date,
1284 'action': self.action,
1284 'action': self.action,
1285 }
1285 }
1286
1286
1287 @hybrid_property
1287 @hybrid_property
1288 def entry_id(self):
1288 def entry_id(self):
1289 return self.user_log_id
1289 return self.user_log_id
1290
1290
1291 @property
1291 @property
1292 def action_as_day(self):
1292 def action_as_day(self):
1293 return datetime.date(*self.action_date.timetuple()[:3])
1293 return datetime.date(*self.action_date.timetuple()[:3])
1294
1294
1295 user = relationship('User')
1295 user = relationship('User')
1296 repository = relationship('Repository', cascade='')
1296 repository = relationship('Repository', cascade='')
1297
1297
1298
1298
1299 class UserGroup(Base, BaseModel):
1299 class UserGroup(Base, BaseModel):
1300 __tablename__ = 'users_groups'
1300 __tablename__ = 'users_groups'
1301 __table_args__ = (
1301 __table_args__ = (
1302 base_table_args,
1302 base_table_args,
1303 )
1303 )
1304
1304
1305 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1305 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1306 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1306 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1307 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1307 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1308 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1308 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1309 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1309 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1310 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1310 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1311 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1311 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1312 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1312 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1313
1313
1314 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1314 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1315 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1315 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1316 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1316 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1317 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1317 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1318 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1318 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1319 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1319 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1320
1320
1321 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1321 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1322 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1322 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1323
1323
1324 @classmethod
1324 @classmethod
1325 def _load_group_data(cls, column):
1325 def _load_group_data(cls, column):
1326 if not column:
1326 if not column:
1327 return {}
1327 return {}
1328
1328
1329 try:
1329 try:
1330 return json.loads(column) or {}
1330 return json.loads(column) or {}
1331 except TypeError:
1331 except TypeError:
1332 return {}
1332 return {}
1333
1333
1334 @hybrid_property
1334 @hybrid_property
1335 def description_safe(self):
1335 def description_safe(self):
1336 from rhodecode.lib import helpers as h
1336 from rhodecode.lib import helpers as h
1337 return h.escape(self.user_group_description)
1337 return h.escape(self.user_group_description)
1338
1338
1339 @hybrid_property
1339 @hybrid_property
1340 def group_data(self):
1340 def group_data(self):
1341 return self._load_group_data(self._group_data)
1341 return self._load_group_data(self._group_data)
1342
1342
1343 @group_data.expression
1343 @group_data.expression
1344 def group_data(self, **kwargs):
1344 def group_data(self, **kwargs):
1345 return self._group_data
1345 return self._group_data
1346
1346
1347 @group_data.setter
1347 @group_data.setter
1348 def group_data(self, val):
1348 def group_data(self, val):
1349 try:
1349 try:
1350 self._group_data = json.dumps(val)
1350 self._group_data = json.dumps(val)
1351 except Exception:
1351 except Exception:
1352 log.error(traceback.format_exc())
1352 log.error(traceback.format_exc())
1353
1353
1354 @classmethod
1354 @classmethod
1355 def _load_sync(cls, group_data):
1355 def _load_sync(cls, group_data):
1356 if group_data:
1356 if group_data:
1357 return group_data.get('extern_type')
1357 return group_data.get('extern_type')
1358
1358
1359 @property
1359 @property
1360 def sync(self):
1360 def sync(self):
1361 return self._load_sync(self.group_data)
1361 return self._load_sync(self.group_data)
1362
1362
1363 def __unicode__(self):
1363 def __unicode__(self):
1364 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1364 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1365 self.users_group_id,
1365 self.users_group_id,
1366 self.users_group_name)
1366 self.users_group_name)
1367
1367
1368 @classmethod
1368 @classmethod
1369 def get_by_group_name(cls, group_name, cache=False,
1369 def get_by_group_name(cls, group_name, cache=False,
1370 case_insensitive=False):
1370 case_insensitive=False):
1371 if case_insensitive:
1371 if case_insensitive:
1372 q = cls.query().filter(func.lower(cls.users_group_name) ==
1372 q = cls.query().filter(func.lower(cls.users_group_name) ==
1373 func.lower(group_name))
1373 func.lower(group_name))
1374
1374
1375 else:
1375 else:
1376 q = cls.query().filter(cls.users_group_name == group_name)
1376 q = cls.query().filter(cls.users_group_name == group_name)
1377 if cache:
1377 if cache:
1378 q = q.options(
1378 q = q.options(
1379 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1379 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1380 return q.scalar()
1380 return q.scalar()
1381
1381
1382 @classmethod
1382 @classmethod
1383 def get(cls, user_group_id, cache=False):
1383 def get(cls, user_group_id, cache=False):
1384 if not user_group_id:
1384 if not user_group_id:
1385 return
1385 return
1386
1386
1387 user_group = cls.query()
1387 user_group = cls.query()
1388 if cache:
1388 if cache:
1389 user_group = user_group.options(
1389 user_group = user_group.options(
1390 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1390 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1391 return user_group.get(user_group_id)
1391 return user_group.get(user_group_id)
1392
1392
1393 def permissions(self, with_admins=True, with_owner=True):
1393 def permissions(self, with_admins=True, with_owner=True):
1394 """
1394 """
1395 Permissions for user groups
1395 Permissions for user groups
1396 """
1396 """
1397 _admin_perm = 'usergroup.admin'
1397 _admin_perm = 'usergroup.admin'
1398
1398
1399 owner_row = []
1399 owner_row = []
1400 if with_owner:
1400 if with_owner:
1401 usr = AttributeDict(self.user.get_dict())
1401 usr = AttributeDict(self.user.get_dict())
1402 usr.owner_row = True
1402 usr.owner_row = True
1403 usr.permission = _admin_perm
1403 usr.permission = _admin_perm
1404 owner_row.append(usr)
1404 owner_row.append(usr)
1405
1405
1406 super_admin_ids = []
1406 super_admin_ids = []
1407 super_admin_rows = []
1407 super_admin_rows = []
1408 if with_admins:
1408 if with_admins:
1409 for usr in User.get_all_super_admins():
1409 for usr in User.get_all_super_admins():
1410 super_admin_ids.append(usr.user_id)
1410 super_admin_ids.append(usr.user_id)
1411 # if this admin is also owner, don't double the record
1411 # if this admin is also owner, don't double the record
1412 if usr.user_id == owner_row[0].user_id:
1412 if usr.user_id == owner_row[0].user_id:
1413 owner_row[0].admin_row = True
1413 owner_row[0].admin_row = True
1414 else:
1414 else:
1415 usr = AttributeDict(usr.get_dict())
1415 usr = AttributeDict(usr.get_dict())
1416 usr.admin_row = True
1416 usr.admin_row = True
1417 usr.permission = _admin_perm
1417 usr.permission = _admin_perm
1418 super_admin_rows.append(usr)
1418 super_admin_rows.append(usr)
1419
1419
1420 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1420 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1421 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1421 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1422 joinedload(UserUserGroupToPerm.user),
1422 joinedload(UserUserGroupToPerm.user),
1423 joinedload(UserUserGroupToPerm.permission),)
1423 joinedload(UserUserGroupToPerm.permission),)
1424
1424
1425 # get owners and admins and permissions. We do a trick of re-writing
1425 # get owners and admins and permissions. We do a trick of re-writing
1426 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1426 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1427 # has a global reference and changing one object propagates to all
1427 # has a global reference and changing one object propagates to all
1428 # others. This means if admin is also an owner admin_row that change
1428 # others. This means if admin is also an owner admin_row that change
1429 # would propagate to both objects
1429 # would propagate to both objects
1430 perm_rows = []
1430 perm_rows = []
1431 for _usr in q.all():
1431 for _usr in q.all():
1432 usr = AttributeDict(_usr.user.get_dict())
1432 usr = AttributeDict(_usr.user.get_dict())
1433 # if this user is also owner/admin, mark as duplicate record
1433 # if this user is also owner/admin, mark as duplicate record
1434 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1434 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1435 usr.duplicate_perm = True
1435 usr.duplicate_perm = True
1436 usr.permission = _usr.permission.permission_name
1436 usr.permission = _usr.permission.permission_name
1437 perm_rows.append(usr)
1437 perm_rows.append(usr)
1438
1438
1439 # filter the perm rows by 'default' first and then sort them by
1439 # filter the perm rows by 'default' first and then sort them by
1440 # admin,write,read,none permissions sorted again alphabetically in
1440 # admin,write,read,none permissions sorted again alphabetically in
1441 # each group
1441 # each group
1442 perm_rows = sorted(perm_rows, key=display_user_sort)
1442 perm_rows = sorted(perm_rows, key=display_user_sort)
1443
1443
1444 return super_admin_rows + owner_row + perm_rows
1444 return super_admin_rows + owner_row + perm_rows
1445
1445
1446 def permission_user_groups(self):
1446 def permission_user_groups(self):
1447 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1447 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1448 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1448 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1449 joinedload(UserGroupUserGroupToPerm.target_user_group),
1449 joinedload(UserGroupUserGroupToPerm.target_user_group),
1450 joinedload(UserGroupUserGroupToPerm.permission),)
1450 joinedload(UserGroupUserGroupToPerm.permission),)
1451
1451
1452 perm_rows = []
1452 perm_rows = []
1453 for _user_group in q.all():
1453 for _user_group in q.all():
1454 usr = AttributeDict(_user_group.user_group.get_dict())
1454 usr = AttributeDict(_user_group.user_group.get_dict())
1455 usr.permission = _user_group.permission.permission_name
1455 usr.permission = _user_group.permission.permission_name
1456 perm_rows.append(usr)
1456 perm_rows.append(usr)
1457
1457
1458 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1458 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1459 return perm_rows
1459 return perm_rows
1460
1460
1461 def _get_default_perms(self, user_group, suffix=''):
1461 def _get_default_perms(self, user_group, suffix=''):
1462 from rhodecode.model.permission import PermissionModel
1462 from rhodecode.model.permission import PermissionModel
1463 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1463 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1464
1464
1465 def get_default_perms(self, suffix=''):
1465 def get_default_perms(self, suffix=''):
1466 return self._get_default_perms(self, suffix)
1466 return self._get_default_perms(self, suffix)
1467
1467
1468 def get_api_data(self, with_group_members=True, include_secrets=False):
1468 def get_api_data(self, with_group_members=True, include_secrets=False):
1469 """
1469 """
1470 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1470 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1471 basically forwarded.
1471 basically forwarded.
1472
1472
1473 """
1473 """
1474 user_group = self
1474 user_group = self
1475 data = {
1475 data = {
1476 'users_group_id': user_group.users_group_id,
1476 'users_group_id': user_group.users_group_id,
1477 'group_name': user_group.users_group_name,
1477 'group_name': user_group.users_group_name,
1478 'group_description': user_group.user_group_description,
1478 'group_description': user_group.user_group_description,
1479 'active': user_group.users_group_active,
1479 'active': user_group.users_group_active,
1480 'owner': user_group.user.username,
1480 'owner': user_group.user.username,
1481 'sync': user_group.sync,
1481 'sync': user_group.sync,
1482 'owner_email': user_group.user.email,
1482 'owner_email': user_group.user.email,
1483 }
1483 }
1484
1484
1485 if with_group_members:
1485 if with_group_members:
1486 users = []
1486 users = []
1487 for user in user_group.members:
1487 for user in user_group.members:
1488 user = user.user
1488 user = user.user
1489 users.append(user.get_api_data(include_secrets=include_secrets))
1489 users.append(user.get_api_data(include_secrets=include_secrets))
1490 data['users'] = users
1490 data['users'] = users
1491
1491
1492 return data
1492 return data
1493
1493
1494
1494
1495 class UserGroupMember(Base, BaseModel):
1495 class UserGroupMember(Base, BaseModel):
1496 __tablename__ = 'users_groups_members'
1496 __tablename__ = 'users_groups_members'
1497 __table_args__ = (
1497 __table_args__ = (
1498 base_table_args,
1498 base_table_args,
1499 )
1499 )
1500
1500
1501 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1501 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1502 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1502 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1503 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1503 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1504
1504
1505 user = relationship('User', lazy='joined')
1505 user = relationship('User', lazy='joined')
1506 users_group = relationship('UserGroup')
1506 users_group = relationship('UserGroup')
1507
1507
1508 def __init__(self, gr_id='', u_id=''):
1508 def __init__(self, gr_id='', u_id=''):
1509 self.users_group_id = gr_id
1509 self.users_group_id = gr_id
1510 self.user_id = u_id
1510 self.user_id = u_id
1511
1511
1512
1512
1513 class RepositoryField(Base, BaseModel):
1513 class RepositoryField(Base, BaseModel):
1514 __tablename__ = 'repositories_fields'
1514 __tablename__ = 'repositories_fields'
1515 __table_args__ = (
1515 __table_args__ = (
1516 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1516 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1517 base_table_args,
1517 base_table_args,
1518 )
1518 )
1519
1519
1520 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1520 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1521
1521
1522 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1522 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1523 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1523 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1524 field_key = Column("field_key", String(250))
1524 field_key = Column("field_key", String(250))
1525 field_label = Column("field_label", String(1024), nullable=False)
1525 field_label = Column("field_label", String(1024), nullable=False)
1526 field_value = Column("field_value", String(10000), nullable=False)
1526 field_value = Column("field_value", String(10000), nullable=False)
1527 field_desc = Column("field_desc", String(1024), nullable=False)
1527 field_desc = Column("field_desc", String(1024), nullable=False)
1528 field_type = Column("field_type", String(255), nullable=False, unique=None)
1528 field_type = Column("field_type", String(255), nullable=False, unique=None)
1529 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1529 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1530
1530
1531 repository = relationship('Repository')
1531 repository = relationship('Repository')
1532
1532
1533 @property
1533 @property
1534 def field_key_prefixed(self):
1534 def field_key_prefixed(self):
1535 return 'ex_%s' % self.field_key
1535 return 'ex_%s' % self.field_key
1536
1536
1537 @classmethod
1537 @classmethod
1538 def un_prefix_key(cls, key):
1538 def un_prefix_key(cls, key):
1539 if key.startswith(cls.PREFIX):
1539 if key.startswith(cls.PREFIX):
1540 return key[len(cls.PREFIX):]
1540 return key[len(cls.PREFIX):]
1541 return key
1541 return key
1542
1542
1543 @classmethod
1543 @classmethod
1544 def get_by_key_name(cls, key, repo):
1544 def get_by_key_name(cls, key, repo):
1545 row = cls.query()\
1545 row = cls.query()\
1546 .filter(cls.repository == repo)\
1546 .filter(cls.repository == repo)\
1547 .filter(cls.field_key == key).scalar()
1547 .filter(cls.field_key == key).scalar()
1548 return row
1548 return row
1549
1549
1550
1550
1551 class Repository(Base, BaseModel):
1551 class Repository(Base, BaseModel):
1552 __tablename__ = 'repositories'
1552 __tablename__ = 'repositories'
1553 __table_args__ = (
1553 __table_args__ = (
1554 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1554 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1555 base_table_args,
1555 base_table_args,
1556 )
1556 )
1557 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1557 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1558 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1558 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1559 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1559 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1560
1560
1561 STATE_CREATED = 'repo_state_created'
1561 STATE_CREATED = 'repo_state_created'
1562 STATE_PENDING = 'repo_state_pending'
1562 STATE_PENDING = 'repo_state_pending'
1563 STATE_ERROR = 'repo_state_error'
1563 STATE_ERROR = 'repo_state_error'
1564
1564
1565 LOCK_AUTOMATIC = 'lock_auto'
1565 LOCK_AUTOMATIC = 'lock_auto'
1566 LOCK_API = 'lock_api'
1566 LOCK_API = 'lock_api'
1567 LOCK_WEB = 'lock_web'
1567 LOCK_WEB = 'lock_web'
1568 LOCK_PULL = 'lock_pull'
1568 LOCK_PULL = 'lock_pull'
1569
1569
1570 NAME_SEP = URL_SEP
1570 NAME_SEP = URL_SEP
1571
1571
1572 repo_id = Column(
1572 repo_id = Column(
1573 "repo_id", Integer(), nullable=False, unique=True, default=None,
1573 "repo_id", Integer(), nullable=False, unique=True, default=None,
1574 primary_key=True)
1574 primary_key=True)
1575 _repo_name = Column(
1575 _repo_name = Column(
1576 "repo_name", Text(), nullable=False, default=None)
1576 "repo_name", Text(), nullable=False, default=None)
1577 _repo_name_hash = Column(
1577 _repo_name_hash = Column(
1578 "repo_name_hash", String(255), nullable=False, unique=True)
1578 "repo_name_hash", String(255), nullable=False, unique=True)
1579 repo_state = Column("repo_state", String(255), nullable=True)
1579 repo_state = Column("repo_state", String(255), nullable=True)
1580
1580
1581 clone_uri = Column(
1581 clone_uri = Column(
1582 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1582 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1583 default=None)
1583 default=None)
1584 push_uri = Column(
1584 push_uri = Column(
1585 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1585 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1586 default=None)
1586 default=None)
1587 repo_type = Column(
1587 repo_type = Column(
1588 "repo_type", String(255), nullable=False, unique=False, default=None)
1588 "repo_type", String(255), nullable=False, unique=False, default=None)
1589 user_id = Column(
1589 user_id = Column(
1590 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1590 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1591 unique=False, default=None)
1591 unique=False, default=None)
1592 private = Column(
1592 private = Column(
1593 "private", Boolean(), nullable=True, unique=None, default=None)
1593 "private", Boolean(), nullable=True, unique=None, default=None)
1594 archived = Column(
1594 archived = Column(
1595 "archived", Boolean(), nullable=True, unique=None, default=None)
1595 "archived", Boolean(), nullable=True, unique=None, default=None)
1596 enable_statistics = Column(
1596 enable_statistics = Column(
1597 "statistics", Boolean(), nullable=True, unique=None, default=True)
1597 "statistics", Boolean(), nullable=True, unique=None, default=True)
1598 enable_downloads = Column(
1598 enable_downloads = Column(
1599 "downloads", Boolean(), nullable=True, unique=None, default=True)
1599 "downloads", Boolean(), nullable=True, unique=None, default=True)
1600 description = Column(
1600 description = Column(
1601 "description", String(10000), nullable=True, unique=None, default=None)
1601 "description", String(10000), nullable=True, unique=None, default=None)
1602 created_on = Column(
1602 created_on = Column(
1603 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1603 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1604 default=datetime.datetime.now)
1604 default=datetime.datetime.now)
1605 updated_on = Column(
1605 updated_on = Column(
1606 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1606 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1607 default=datetime.datetime.now)
1607 default=datetime.datetime.now)
1608 _landing_revision = Column(
1608 _landing_revision = Column(
1609 "landing_revision", String(255), nullable=False, unique=False,
1609 "landing_revision", String(255), nullable=False, unique=False,
1610 default=None)
1610 default=None)
1611 enable_locking = Column(
1611 enable_locking = Column(
1612 "enable_locking", Boolean(), nullable=False, unique=None,
1612 "enable_locking", Boolean(), nullable=False, unique=None,
1613 default=False)
1613 default=False)
1614 _locked = Column(
1614 _locked = Column(
1615 "locked", String(255), nullable=True, unique=False, default=None)
1615 "locked", String(255), nullable=True, unique=False, default=None)
1616 _changeset_cache = Column(
1616 _changeset_cache = Column(
1617 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1617 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1618
1618
1619 fork_id = Column(
1619 fork_id = Column(
1620 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1620 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1621 nullable=True, unique=False, default=None)
1621 nullable=True, unique=False, default=None)
1622 group_id = Column(
1622 group_id = Column(
1623 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1623 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1624 unique=False, default=None)
1624 unique=False, default=None)
1625
1625
1626 user = relationship('User', lazy='joined')
1626 user = relationship('User', lazy='joined')
1627 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1627 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1628 group = relationship('RepoGroup', lazy='joined')
1628 group = relationship('RepoGroup', lazy='joined')
1629 repo_to_perm = relationship(
1629 repo_to_perm = relationship(
1630 'UserRepoToPerm', cascade='all',
1630 'UserRepoToPerm', cascade='all',
1631 order_by='UserRepoToPerm.repo_to_perm_id')
1631 order_by='UserRepoToPerm.repo_to_perm_id')
1632 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1632 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1633 stats = relationship('Statistics', cascade='all', uselist=False)
1633 stats = relationship('Statistics', cascade='all', uselist=False)
1634
1634
1635 followers = relationship(
1635 followers = relationship(
1636 'UserFollowing',
1636 'UserFollowing',
1637 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1637 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1638 cascade='all')
1638 cascade='all')
1639 extra_fields = relationship(
1639 extra_fields = relationship(
1640 'RepositoryField', cascade="all, delete, delete-orphan")
1640 'RepositoryField', cascade="all, delete, delete-orphan")
1641 logs = relationship('UserLog')
1641 logs = relationship('UserLog')
1642 comments = relationship(
1642 comments = relationship(
1643 'ChangesetComment', cascade="all, delete, delete-orphan")
1643 'ChangesetComment', cascade="all, delete, delete-orphan")
1644 pull_requests_source = relationship(
1644 pull_requests_source = relationship(
1645 'PullRequest',
1645 'PullRequest',
1646 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1646 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1647 cascade="all, delete, delete-orphan")
1647 cascade="all, delete, delete-orphan")
1648 pull_requests_target = relationship(
1648 pull_requests_target = relationship(
1649 'PullRequest',
1649 'PullRequest',
1650 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1650 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1651 cascade="all, delete, delete-orphan")
1651 cascade="all, delete, delete-orphan")
1652 ui = relationship('RepoRhodeCodeUi', cascade="all")
1652 ui = relationship('RepoRhodeCodeUi', cascade="all")
1653 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1653 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1654 integrations = relationship('Integration',
1654 integrations = relationship('Integration',
1655 cascade="all, delete, delete-orphan")
1655 cascade="all, delete, delete-orphan")
1656
1656
1657 scoped_tokens = relationship('UserApiKeys', cascade="all")
1657 scoped_tokens = relationship('UserApiKeys', cascade="all")
1658
1658
1659 def __unicode__(self):
1659 def __unicode__(self):
1660 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1660 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1661 safe_unicode(self.repo_name))
1661 safe_unicode(self.repo_name))
1662
1662
1663 @hybrid_property
1663 @hybrid_property
1664 def description_safe(self):
1664 def description_safe(self):
1665 from rhodecode.lib import helpers as h
1665 from rhodecode.lib import helpers as h
1666 return h.escape(self.description)
1666 return h.escape(self.description)
1667
1667
1668 @hybrid_property
1668 @hybrid_property
1669 def landing_rev(self):
1669 def landing_rev(self):
1670 # always should return [rev_type, rev]
1670 # always should return [rev_type, rev]
1671 if self._landing_revision:
1671 if self._landing_revision:
1672 _rev_info = self._landing_revision.split(':')
1672 _rev_info = self._landing_revision.split(':')
1673 if len(_rev_info) < 2:
1673 if len(_rev_info) < 2:
1674 _rev_info.insert(0, 'rev')
1674 _rev_info.insert(0, 'rev')
1675 return [_rev_info[0], _rev_info[1]]
1675 return [_rev_info[0], _rev_info[1]]
1676 return [None, None]
1676 return [None, None]
1677
1677
1678 @landing_rev.setter
1678 @landing_rev.setter
1679 def landing_rev(self, val):
1679 def landing_rev(self, val):
1680 if ':' not in val:
1680 if ':' not in val:
1681 raise ValueError('value must be delimited with `:` and consist '
1681 raise ValueError('value must be delimited with `:` and consist '
1682 'of <rev_type>:<rev>, got %s instead' % val)
1682 'of <rev_type>:<rev>, got %s instead' % val)
1683 self._landing_revision = val
1683 self._landing_revision = val
1684
1684
1685 @hybrid_property
1685 @hybrid_property
1686 def locked(self):
1686 def locked(self):
1687 if self._locked:
1687 if self._locked:
1688 user_id, timelocked, reason = self._locked.split(':')
1688 user_id, timelocked, reason = self._locked.split(':')
1689 lock_values = int(user_id), timelocked, reason
1689 lock_values = int(user_id), timelocked, reason
1690 else:
1690 else:
1691 lock_values = [None, None, None]
1691 lock_values = [None, None, None]
1692 return lock_values
1692 return lock_values
1693
1693
1694 @locked.setter
1694 @locked.setter
1695 def locked(self, val):
1695 def locked(self, val):
1696 if val and isinstance(val, (list, tuple)):
1696 if val and isinstance(val, (list, tuple)):
1697 self._locked = ':'.join(map(str, val))
1697 self._locked = ':'.join(map(str, val))
1698 else:
1698 else:
1699 self._locked = None
1699 self._locked = None
1700
1700
1701 @hybrid_property
1701 @hybrid_property
1702 def changeset_cache(self):
1702 def changeset_cache(self):
1703 from rhodecode.lib.vcs.backends.base import EmptyCommit
1703 from rhodecode.lib.vcs.backends.base import EmptyCommit
1704 dummy = EmptyCommit().__json__()
1704 dummy = EmptyCommit().__json__()
1705 if not self._changeset_cache:
1705 if not self._changeset_cache:
1706 return dummy
1706 return dummy
1707 try:
1707 try:
1708 return json.loads(self._changeset_cache)
1708 return json.loads(self._changeset_cache)
1709 except TypeError:
1709 except TypeError:
1710 return dummy
1710 return dummy
1711 except Exception:
1711 except Exception:
1712 log.error(traceback.format_exc())
1712 log.error(traceback.format_exc())
1713 return dummy
1713 return dummy
1714
1714
1715 @changeset_cache.setter
1715 @changeset_cache.setter
1716 def changeset_cache(self, val):
1716 def changeset_cache(self, val):
1717 try:
1717 try:
1718 self._changeset_cache = json.dumps(val)
1718 self._changeset_cache = json.dumps(val)
1719 except Exception:
1719 except Exception:
1720 log.error(traceback.format_exc())
1720 log.error(traceback.format_exc())
1721
1721
1722 @hybrid_property
1722 @hybrid_property
1723 def repo_name(self):
1723 def repo_name(self):
1724 return self._repo_name
1724 return self._repo_name
1725
1725
1726 @repo_name.setter
1726 @repo_name.setter
1727 def repo_name(self, value):
1727 def repo_name(self, value):
1728 self._repo_name = value
1728 self._repo_name = value
1729 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1729 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1730
1730
1731 @classmethod
1731 @classmethod
1732 def normalize_repo_name(cls, repo_name):
1732 def normalize_repo_name(cls, repo_name):
1733 """
1733 """
1734 Normalizes os specific repo_name to the format internally stored inside
1734 Normalizes os specific repo_name to the format internally stored inside
1735 database using URL_SEP
1735 database using URL_SEP
1736
1736
1737 :param cls:
1737 :param cls:
1738 :param repo_name:
1738 :param repo_name:
1739 """
1739 """
1740 return cls.NAME_SEP.join(repo_name.split(os.sep))
1740 return cls.NAME_SEP.join(repo_name.split(os.sep))
1741
1741
1742 @classmethod
1742 @classmethod
1743 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1743 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1744 session = Session()
1744 session = Session()
1745 q = session.query(cls).filter(cls.repo_name == repo_name)
1745 q = session.query(cls).filter(cls.repo_name == repo_name)
1746
1746
1747 if cache:
1747 if cache:
1748 if identity_cache:
1748 if identity_cache:
1749 val = cls.identity_cache(session, 'repo_name', repo_name)
1749 val = cls.identity_cache(session, 'repo_name', repo_name)
1750 if val:
1750 if val:
1751 return val
1751 return val
1752 else:
1752 else:
1753 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1753 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1754 q = q.options(
1754 q = q.options(
1755 FromCache("sql_cache_short", cache_key))
1755 FromCache("sql_cache_short", cache_key))
1756
1756
1757 return q.scalar()
1757 return q.scalar()
1758
1758
1759 @classmethod
1759 @classmethod
1760 def get_by_id_or_repo_name(cls, repoid):
1760 def get_by_id_or_repo_name(cls, repoid):
1761 if isinstance(repoid, (int, long)):
1761 if isinstance(repoid, (int, long)):
1762 try:
1762 try:
1763 repo = cls.get(repoid)
1763 repo = cls.get(repoid)
1764 except ValueError:
1764 except ValueError:
1765 repo = None
1765 repo = None
1766 else:
1766 else:
1767 repo = cls.get_by_repo_name(repoid)
1767 repo = cls.get_by_repo_name(repoid)
1768 return repo
1768 return repo
1769
1769
1770 @classmethod
1770 @classmethod
1771 def get_by_full_path(cls, repo_full_path):
1771 def get_by_full_path(cls, repo_full_path):
1772 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1772 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1773 repo_name = cls.normalize_repo_name(repo_name)
1773 repo_name = cls.normalize_repo_name(repo_name)
1774 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1774 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1775
1775
1776 @classmethod
1776 @classmethod
1777 def get_repo_forks(cls, repo_id):
1777 def get_repo_forks(cls, repo_id):
1778 return cls.query().filter(Repository.fork_id == repo_id)
1778 return cls.query().filter(Repository.fork_id == repo_id)
1779
1779
1780 @classmethod
1780 @classmethod
1781 def base_path(cls):
1781 def base_path(cls):
1782 """
1782 """
1783 Returns base path when all repos are stored
1783 Returns base path when all repos are stored
1784
1784
1785 :param cls:
1785 :param cls:
1786 """
1786 """
1787 q = Session().query(RhodeCodeUi)\
1787 q = Session().query(RhodeCodeUi)\
1788 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1788 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1789 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1789 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1790 return q.one().ui_value
1790 return q.one().ui_value
1791
1791
1792 @classmethod
1792 @classmethod
1793 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1793 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1794 case_insensitive=True, archived=False):
1794 case_insensitive=True, archived=False):
1795 q = Repository.query()
1795 q = Repository.query()
1796
1796
1797 if not archived:
1797 if not archived:
1798 q = q.filter(Repository.archived.isnot(true()))
1798 q = q.filter(Repository.archived.isnot(true()))
1799
1799
1800 if not isinstance(user_id, Optional):
1800 if not isinstance(user_id, Optional):
1801 q = q.filter(Repository.user_id == user_id)
1801 q = q.filter(Repository.user_id == user_id)
1802
1802
1803 if not isinstance(group_id, Optional):
1803 if not isinstance(group_id, Optional):
1804 q = q.filter(Repository.group_id == group_id)
1804 q = q.filter(Repository.group_id == group_id)
1805
1805
1806 if case_insensitive:
1806 if case_insensitive:
1807 q = q.order_by(func.lower(Repository.repo_name))
1807 q = q.order_by(func.lower(Repository.repo_name))
1808 else:
1808 else:
1809 q = q.order_by(Repository.repo_name)
1809 q = q.order_by(Repository.repo_name)
1810
1810
1811 return q.all()
1811 return q.all()
1812
1812
1813 @property
1813 @property
1814 def forks(self):
1814 def forks(self):
1815 """
1815 """
1816 Return forks of this repo
1816 Return forks of this repo
1817 """
1817 """
1818 return Repository.get_repo_forks(self.repo_id)
1818 return Repository.get_repo_forks(self.repo_id)
1819
1819
1820 @property
1820 @property
1821 def parent(self):
1821 def parent(self):
1822 """
1822 """
1823 Returns fork parent
1823 Returns fork parent
1824 """
1824 """
1825 return self.fork
1825 return self.fork
1826
1826
1827 @property
1827 @property
1828 def just_name(self):
1828 def just_name(self):
1829 return self.repo_name.split(self.NAME_SEP)[-1]
1829 return self.repo_name.split(self.NAME_SEP)[-1]
1830
1830
1831 @property
1831 @property
1832 def groups_with_parents(self):
1832 def groups_with_parents(self):
1833 groups = []
1833 groups = []
1834 if self.group is None:
1834 if self.group is None:
1835 return groups
1835 return groups
1836
1836
1837 cur_gr = self.group
1837 cur_gr = self.group
1838 groups.insert(0, cur_gr)
1838 groups.insert(0, cur_gr)
1839 while 1:
1839 while 1:
1840 gr = getattr(cur_gr, 'parent_group', None)
1840 gr = getattr(cur_gr, 'parent_group', None)
1841 cur_gr = cur_gr.parent_group
1841 cur_gr = cur_gr.parent_group
1842 if gr is None:
1842 if gr is None:
1843 break
1843 break
1844 groups.insert(0, gr)
1844 groups.insert(0, gr)
1845
1845
1846 return groups
1846 return groups
1847
1847
1848 @property
1848 @property
1849 def groups_and_repo(self):
1849 def groups_and_repo(self):
1850 return self.groups_with_parents, self
1850 return self.groups_with_parents, self
1851
1851
1852 @LazyProperty
1852 @LazyProperty
1853 def repo_path(self):
1853 def repo_path(self):
1854 """
1854 """
1855 Returns base full path for that repository means where it actually
1855 Returns base full path for that repository means where it actually
1856 exists on a filesystem
1856 exists on a filesystem
1857 """
1857 """
1858 q = Session().query(RhodeCodeUi).filter(
1858 q = Session().query(RhodeCodeUi).filter(
1859 RhodeCodeUi.ui_key == self.NAME_SEP)
1859 RhodeCodeUi.ui_key == self.NAME_SEP)
1860 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1860 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1861 return q.one().ui_value
1861 return q.one().ui_value
1862
1862
1863 @property
1863 @property
1864 def repo_full_path(self):
1864 def repo_full_path(self):
1865 p = [self.repo_path]
1865 p = [self.repo_path]
1866 # we need to split the name by / since this is how we store the
1866 # we need to split the name by / since this is how we store the
1867 # names in the database, but that eventually needs to be converted
1867 # names in the database, but that eventually needs to be converted
1868 # into a valid system path
1868 # into a valid system path
1869 p += self.repo_name.split(self.NAME_SEP)
1869 p += self.repo_name.split(self.NAME_SEP)
1870 return os.path.join(*map(safe_unicode, p))
1870 return os.path.join(*map(safe_unicode, p))
1871
1871
1872 @property
1872 @property
1873 def cache_keys(self):
1873 def cache_keys(self):
1874 """
1874 """
1875 Returns associated cache keys for that repo
1875 Returns associated cache keys for that repo
1876 """
1876 """
1877 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1877 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1878 repo_id=self.repo_id)
1878 repo_id=self.repo_id)
1879 return CacheKey.query()\
1879 return CacheKey.query()\
1880 .filter(CacheKey.cache_args == invalidation_namespace)\
1880 .filter(CacheKey.cache_args == invalidation_namespace)\
1881 .order_by(CacheKey.cache_key)\
1881 .order_by(CacheKey.cache_key)\
1882 .all()
1882 .all()
1883
1883
1884 @property
1884 @property
1885 def cached_diffs_relative_dir(self):
1885 def cached_diffs_relative_dir(self):
1886 """
1886 """
1887 Return a relative to the repository store path of cached diffs
1887 Return a relative to the repository store path of cached diffs
1888 used for safe display for users, who shouldn't know the absolute store
1888 used for safe display for users, who shouldn't know the absolute store
1889 path
1889 path
1890 """
1890 """
1891 return os.path.join(
1891 return os.path.join(
1892 os.path.dirname(self.repo_name),
1892 os.path.dirname(self.repo_name),
1893 self.cached_diffs_dir.split(os.path.sep)[-1])
1893 self.cached_diffs_dir.split(os.path.sep)[-1])
1894
1894
1895 @property
1895 @property
1896 def cached_diffs_dir(self):
1896 def cached_diffs_dir(self):
1897 path = self.repo_full_path
1897 path = self.repo_full_path
1898 return os.path.join(
1898 return os.path.join(
1899 os.path.dirname(path),
1899 os.path.dirname(path),
1900 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1900 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1901
1901
1902 def cached_diffs(self):
1902 def cached_diffs(self):
1903 diff_cache_dir = self.cached_diffs_dir
1903 diff_cache_dir = self.cached_diffs_dir
1904 if os.path.isdir(diff_cache_dir):
1904 if os.path.isdir(diff_cache_dir):
1905 return os.listdir(diff_cache_dir)
1905 return os.listdir(diff_cache_dir)
1906 return []
1906 return []
1907
1907
1908 def shadow_repos(self):
1908 def shadow_repos(self):
1909 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1909 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1910 return [
1910 return [
1911 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1911 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1912 if x.startswith(shadow_repos_pattern)]
1912 if x.startswith(shadow_repos_pattern)]
1913
1913
1914 def get_new_name(self, repo_name):
1914 def get_new_name(self, repo_name):
1915 """
1915 """
1916 returns new full repository name based on assigned group and new new
1916 returns new full repository name based on assigned group and new new
1917
1917
1918 :param group_name:
1918 :param group_name:
1919 """
1919 """
1920 path_prefix = self.group.full_path_splitted if self.group else []
1920 path_prefix = self.group.full_path_splitted if self.group else []
1921 return self.NAME_SEP.join(path_prefix + [repo_name])
1921 return self.NAME_SEP.join(path_prefix + [repo_name])
1922
1922
1923 @property
1923 @property
1924 def _config(self):
1924 def _config(self):
1925 """
1925 """
1926 Returns db based config object.
1926 Returns db based config object.
1927 """
1927 """
1928 from rhodecode.lib.utils import make_db_config
1928 from rhodecode.lib.utils import make_db_config
1929 return make_db_config(clear_session=False, repo=self)
1929 return make_db_config(clear_session=False, repo=self)
1930
1930
1931 def permissions(self, with_admins=True, with_owner=True):
1931 def permissions(self, with_admins=True, with_owner=True):
1932 """
1932 """
1933 Permissions for repositories
1933 Permissions for repositories
1934 """
1934 """
1935 _admin_perm = 'repository.admin'
1935 _admin_perm = 'repository.admin'
1936
1936
1937 owner_row = []
1937 owner_row = []
1938 if with_owner:
1938 if with_owner:
1939 usr = AttributeDict(self.user.get_dict())
1939 usr = AttributeDict(self.user.get_dict())
1940 usr.owner_row = True
1940 usr.owner_row = True
1941 usr.permission = _admin_perm
1941 usr.permission = _admin_perm
1942 usr.permission_id = None
1942 usr.permission_id = None
1943 owner_row.append(usr)
1943 owner_row.append(usr)
1944
1944
1945 super_admin_ids = []
1945 super_admin_ids = []
1946 super_admin_rows = []
1946 super_admin_rows = []
1947 if with_admins:
1947 if with_admins:
1948 for usr in User.get_all_super_admins():
1948 for usr in User.get_all_super_admins():
1949 super_admin_ids.append(usr.user_id)
1949 super_admin_ids.append(usr.user_id)
1950 # if this admin is also owner, don't double the record
1950 # if this admin is also owner, don't double the record
1951 if usr.user_id == owner_row[0].user_id:
1951 if usr.user_id == owner_row[0].user_id:
1952 owner_row[0].admin_row = True
1952 owner_row[0].admin_row = True
1953 else:
1953 else:
1954 usr = AttributeDict(usr.get_dict())
1954 usr = AttributeDict(usr.get_dict())
1955 usr.admin_row = True
1955 usr.admin_row = True
1956 usr.permission = _admin_perm
1956 usr.permission = _admin_perm
1957 usr.permission_id = None
1957 usr.permission_id = None
1958 super_admin_rows.append(usr)
1958 super_admin_rows.append(usr)
1959
1959
1960 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1960 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1961 q = q.options(joinedload(UserRepoToPerm.repository),
1961 q = q.options(joinedload(UserRepoToPerm.repository),
1962 joinedload(UserRepoToPerm.user),
1962 joinedload(UserRepoToPerm.user),
1963 joinedload(UserRepoToPerm.permission),)
1963 joinedload(UserRepoToPerm.permission),)
1964
1964
1965 # get owners and admins and permissions. We do a trick of re-writing
1965 # get owners and admins and permissions. We do a trick of re-writing
1966 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1966 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1967 # has a global reference and changing one object propagates to all
1967 # has a global reference and changing one object propagates to all
1968 # others. This means if admin is also an owner admin_row that change
1968 # others. This means if admin is also an owner admin_row that change
1969 # would propagate to both objects
1969 # would propagate to both objects
1970 perm_rows = []
1970 perm_rows = []
1971 for _usr in q.all():
1971 for _usr in q.all():
1972 usr = AttributeDict(_usr.user.get_dict())
1972 usr = AttributeDict(_usr.user.get_dict())
1973 # if this user is also owner/admin, mark as duplicate record
1973 # if this user is also owner/admin, mark as duplicate record
1974 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1974 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1975 usr.duplicate_perm = True
1975 usr.duplicate_perm = True
1976 # also check if this permission is maybe used by branch_permissions
1976 # also check if this permission is maybe used by branch_permissions
1977 if _usr.branch_perm_entry:
1977 if _usr.branch_perm_entry:
1978 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1978 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1979
1979
1980 usr.permission = _usr.permission.permission_name
1980 usr.permission = _usr.permission.permission_name
1981 usr.permission_id = _usr.repo_to_perm_id
1981 usr.permission_id = _usr.repo_to_perm_id
1982 perm_rows.append(usr)
1982 perm_rows.append(usr)
1983
1983
1984 # filter the perm rows by 'default' first and then sort them by
1984 # filter the perm rows by 'default' first and then sort them by
1985 # admin,write,read,none permissions sorted again alphabetically in
1985 # admin,write,read,none permissions sorted again alphabetically in
1986 # each group
1986 # each group
1987 perm_rows = sorted(perm_rows, key=display_user_sort)
1987 perm_rows = sorted(perm_rows, key=display_user_sort)
1988
1988
1989 return super_admin_rows + owner_row + perm_rows
1989 return super_admin_rows + owner_row + perm_rows
1990
1990
1991 def permission_user_groups(self):
1991 def permission_user_groups(self):
1992 q = UserGroupRepoToPerm.query().filter(
1992 q = UserGroupRepoToPerm.query().filter(
1993 UserGroupRepoToPerm.repository == self)
1993 UserGroupRepoToPerm.repository == self)
1994 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1994 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1995 joinedload(UserGroupRepoToPerm.users_group),
1995 joinedload(UserGroupRepoToPerm.users_group),
1996 joinedload(UserGroupRepoToPerm.permission),)
1996 joinedload(UserGroupRepoToPerm.permission),)
1997
1997
1998 perm_rows = []
1998 perm_rows = []
1999 for _user_group in q.all():
1999 for _user_group in q.all():
2000 usr = AttributeDict(_user_group.users_group.get_dict())
2000 usr = AttributeDict(_user_group.users_group.get_dict())
2001 usr.permission = _user_group.permission.permission_name
2001 usr.permission = _user_group.permission.permission_name
2002 perm_rows.append(usr)
2002 perm_rows.append(usr)
2003
2003
2004 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2004 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2005 return perm_rows
2005 return perm_rows
2006
2006
2007 def get_api_data(self, include_secrets=False):
2007 def get_api_data(self, include_secrets=False):
2008 """
2008 """
2009 Common function for generating repo api data
2009 Common function for generating repo api data
2010
2010
2011 :param include_secrets: See :meth:`User.get_api_data`.
2011 :param include_secrets: See :meth:`User.get_api_data`.
2012
2012
2013 """
2013 """
2014 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2014 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2015 # move this methods on models level.
2015 # move this methods on models level.
2016 from rhodecode.model.settings import SettingsModel
2016 from rhodecode.model.settings import SettingsModel
2017 from rhodecode.model.repo import RepoModel
2017 from rhodecode.model.repo import RepoModel
2018
2018
2019 repo = self
2019 repo = self
2020 _user_id, _time, _reason = self.locked
2020 _user_id, _time, _reason = self.locked
2021
2021
2022 data = {
2022 data = {
2023 'repo_id': repo.repo_id,
2023 'repo_id': repo.repo_id,
2024 'repo_name': repo.repo_name,
2024 'repo_name': repo.repo_name,
2025 'repo_type': repo.repo_type,
2025 'repo_type': repo.repo_type,
2026 'clone_uri': repo.clone_uri or '',
2026 'clone_uri': repo.clone_uri or '',
2027 'push_uri': repo.push_uri or '',
2027 'push_uri': repo.push_uri or '',
2028 'url': RepoModel().get_url(self),
2028 'url': RepoModel().get_url(self),
2029 'private': repo.private,
2029 'private': repo.private,
2030 'created_on': repo.created_on,
2030 'created_on': repo.created_on,
2031 'description': repo.description_safe,
2031 'description': repo.description_safe,
2032 'landing_rev': repo.landing_rev,
2032 'landing_rev': repo.landing_rev,
2033 'owner': repo.user.username,
2033 'owner': repo.user.username,
2034 'fork_of': repo.fork.repo_name if repo.fork else None,
2034 'fork_of': repo.fork.repo_name if repo.fork else None,
2035 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2035 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2036 'enable_statistics': repo.enable_statistics,
2036 'enable_statistics': repo.enable_statistics,
2037 'enable_locking': repo.enable_locking,
2037 'enable_locking': repo.enable_locking,
2038 'enable_downloads': repo.enable_downloads,
2038 'enable_downloads': repo.enable_downloads,
2039 'last_changeset': repo.changeset_cache,
2039 'last_changeset': repo.changeset_cache,
2040 'locked_by': User.get(_user_id).get_api_data(
2040 'locked_by': User.get(_user_id).get_api_data(
2041 include_secrets=include_secrets) if _user_id else None,
2041 include_secrets=include_secrets) if _user_id else None,
2042 'locked_date': time_to_datetime(_time) if _time else None,
2042 'locked_date': time_to_datetime(_time) if _time else None,
2043 'lock_reason': _reason if _reason else None,
2043 'lock_reason': _reason if _reason else None,
2044 }
2044 }
2045
2045
2046 # TODO: mikhail: should be per-repo settings here
2046 # TODO: mikhail: should be per-repo settings here
2047 rc_config = SettingsModel().get_all_settings()
2047 rc_config = SettingsModel().get_all_settings()
2048 repository_fields = str2bool(
2048 repository_fields = str2bool(
2049 rc_config.get('rhodecode_repository_fields'))
2049 rc_config.get('rhodecode_repository_fields'))
2050 if repository_fields:
2050 if repository_fields:
2051 for f in self.extra_fields:
2051 for f in self.extra_fields:
2052 data[f.field_key_prefixed] = f.field_value
2052 data[f.field_key_prefixed] = f.field_value
2053
2053
2054 return data
2054 return data
2055
2055
2056 @classmethod
2056 @classmethod
2057 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2057 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2058 if not lock_time:
2058 if not lock_time:
2059 lock_time = time.time()
2059 lock_time = time.time()
2060 if not lock_reason:
2060 if not lock_reason:
2061 lock_reason = cls.LOCK_AUTOMATIC
2061 lock_reason = cls.LOCK_AUTOMATIC
2062 repo.locked = [user_id, lock_time, lock_reason]
2062 repo.locked = [user_id, lock_time, lock_reason]
2063 Session().add(repo)
2063 Session().add(repo)
2064 Session().commit()
2064 Session().commit()
2065
2065
2066 @classmethod
2066 @classmethod
2067 def unlock(cls, repo):
2067 def unlock(cls, repo):
2068 repo.locked = None
2068 repo.locked = None
2069 Session().add(repo)
2069 Session().add(repo)
2070 Session().commit()
2070 Session().commit()
2071
2071
2072 @classmethod
2072 @classmethod
2073 def getlock(cls, repo):
2073 def getlock(cls, repo):
2074 return repo.locked
2074 return repo.locked
2075
2075
2076 def is_user_lock(self, user_id):
2076 def is_user_lock(self, user_id):
2077 if self.lock[0]:
2077 if self.lock[0]:
2078 lock_user_id = safe_int(self.lock[0])
2078 lock_user_id = safe_int(self.lock[0])
2079 user_id = safe_int(user_id)
2079 user_id = safe_int(user_id)
2080 # both are ints, and they are equal
2080 # both are ints, and they are equal
2081 return all([lock_user_id, user_id]) and lock_user_id == user_id
2081 return all([lock_user_id, user_id]) and lock_user_id == user_id
2082
2082
2083 return False
2083 return False
2084
2084
2085 def get_locking_state(self, action, user_id, only_when_enabled=True):
2085 def get_locking_state(self, action, user_id, only_when_enabled=True):
2086 """
2086 """
2087 Checks locking on this repository, if locking is enabled and lock is
2087 Checks locking on this repository, if locking is enabled and lock is
2088 present returns a tuple of make_lock, locked, locked_by.
2088 present returns a tuple of make_lock, locked, locked_by.
2089 make_lock can have 3 states None (do nothing) True, make lock
2089 make_lock can have 3 states None (do nothing) True, make lock
2090 False release lock, This value is later propagated to hooks, which
2090 False release lock, This value is later propagated to hooks, which
2091 do the locking. Think about this as signals passed to hooks what to do.
2091 do the locking. Think about this as signals passed to hooks what to do.
2092
2092
2093 """
2093 """
2094 # TODO: johbo: This is part of the business logic and should be moved
2094 # TODO: johbo: This is part of the business logic and should be moved
2095 # into the RepositoryModel.
2095 # into the RepositoryModel.
2096
2096
2097 if action not in ('push', 'pull'):
2097 if action not in ('push', 'pull'):
2098 raise ValueError("Invalid action value: %s" % repr(action))
2098 raise ValueError("Invalid action value: %s" % repr(action))
2099
2099
2100 # defines if locked error should be thrown to user
2100 # defines if locked error should be thrown to user
2101 currently_locked = False
2101 currently_locked = False
2102 # defines if new lock should be made, tri-state
2102 # defines if new lock should be made, tri-state
2103 make_lock = None
2103 make_lock = None
2104 repo = self
2104 repo = self
2105 user = User.get(user_id)
2105 user = User.get(user_id)
2106
2106
2107 lock_info = repo.locked
2107 lock_info = repo.locked
2108
2108
2109 if repo and (repo.enable_locking or not only_when_enabled):
2109 if repo and (repo.enable_locking or not only_when_enabled):
2110 if action == 'push':
2110 if action == 'push':
2111 # check if it's already locked !, if it is compare users
2111 # check if it's already locked !, if it is compare users
2112 locked_by_user_id = lock_info[0]
2112 locked_by_user_id = lock_info[0]
2113 if user.user_id == locked_by_user_id:
2113 if user.user_id == locked_by_user_id:
2114 log.debug(
2114 log.debug(
2115 'Got `push` action from user %s, now unlocking', user)
2115 'Got `push` action from user %s, now unlocking', user)
2116 # unlock if we have push from user who locked
2116 # unlock if we have push from user who locked
2117 make_lock = False
2117 make_lock = False
2118 else:
2118 else:
2119 # we're not the same user who locked, ban with
2119 # we're not the same user who locked, ban with
2120 # code defined in settings (default is 423 HTTP Locked) !
2120 # code defined in settings (default is 423 HTTP Locked) !
2121 log.debug('Repo %s is currently locked by %s', repo, user)
2121 log.debug('Repo %s is currently locked by %s', repo, user)
2122 currently_locked = True
2122 currently_locked = True
2123 elif action == 'pull':
2123 elif action == 'pull':
2124 # [0] user [1] date
2124 # [0] user [1] date
2125 if lock_info[0] and lock_info[1]:
2125 if lock_info[0] and lock_info[1]:
2126 log.debug('Repo %s is currently locked by %s', repo, user)
2126 log.debug('Repo %s is currently locked by %s', repo, user)
2127 currently_locked = True
2127 currently_locked = True
2128 else:
2128 else:
2129 log.debug('Setting lock on repo %s by %s', repo, user)
2129 log.debug('Setting lock on repo %s by %s', repo, user)
2130 make_lock = True
2130 make_lock = True
2131
2131
2132 else:
2132 else:
2133 log.debug('Repository %s do not have locking enabled', repo)
2133 log.debug('Repository %s do not have locking enabled', repo)
2134
2134
2135 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2135 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2136 make_lock, currently_locked, lock_info)
2136 make_lock, currently_locked, lock_info)
2137
2137
2138 from rhodecode.lib.auth import HasRepoPermissionAny
2138 from rhodecode.lib.auth import HasRepoPermissionAny
2139 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2139 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2140 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2140 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2141 # if we don't have at least write permission we cannot make a lock
2141 # if we don't have at least write permission we cannot make a lock
2142 log.debug('lock state reset back to FALSE due to lack '
2142 log.debug('lock state reset back to FALSE due to lack '
2143 'of at least read permission')
2143 'of at least read permission')
2144 make_lock = False
2144 make_lock = False
2145
2145
2146 return make_lock, currently_locked, lock_info
2146 return make_lock, currently_locked, lock_info
2147
2147
2148 @property
2148 @property
2149 def last_db_change(self):
2149 def last_db_change(self):
2150 return self.updated_on
2150 return self.updated_on
2151
2151
2152 @property
2152 @property
2153 def clone_uri_hidden(self):
2153 def clone_uri_hidden(self):
2154 clone_uri = self.clone_uri
2154 clone_uri = self.clone_uri
2155 if clone_uri:
2155 if clone_uri:
2156 import urlobject
2156 import urlobject
2157 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2157 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2158 if url_obj.password:
2158 if url_obj.password:
2159 clone_uri = url_obj.with_password('*****')
2159 clone_uri = url_obj.with_password('*****')
2160 return clone_uri
2160 return clone_uri
2161
2161
2162 @property
2162 @property
2163 def push_uri_hidden(self):
2163 def push_uri_hidden(self):
2164 push_uri = self.push_uri
2164 push_uri = self.push_uri
2165 if push_uri:
2165 if push_uri:
2166 import urlobject
2166 import urlobject
2167 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2167 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2168 if url_obj.password:
2168 if url_obj.password:
2169 push_uri = url_obj.with_password('*****')
2169 push_uri = url_obj.with_password('*****')
2170 return push_uri
2170 return push_uri
2171
2171
2172 def clone_url(self, **override):
2172 def clone_url(self, **override):
2173 from rhodecode.model.settings import SettingsModel
2173 from rhodecode.model.settings import SettingsModel
2174
2174
2175 uri_tmpl = None
2175 uri_tmpl = None
2176 if 'with_id' in override:
2176 if 'with_id' in override:
2177 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2177 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2178 del override['with_id']
2178 del override['with_id']
2179
2179
2180 if 'uri_tmpl' in override:
2180 if 'uri_tmpl' in override:
2181 uri_tmpl = override['uri_tmpl']
2181 uri_tmpl = override['uri_tmpl']
2182 del override['uri_tmpl']
2182 del override['uri_tmpl']
2183
2183
2184 ssh = False
2184 ssh = False
2185 if 'ssh' in override:
2185 if 'ssh' in override:
2186 ssh = True
2186 ssh = True
2187 del override['ssh']
2187 del override['ssh']
2188
2188
2189 # we didn't override our tmpl from **overrides
2189 # we didn't override our tmpl from **overrides
2190 if not uri_tmpl:
2190 if not uri_tmpl:
2191 rc_config = SettingsModel().get_all_settings(cache=True)
2191 rc_config = SettingsModel().get_all_settings(cache=True)
2192 if ssh:
2192 if ssh:
2193 uri_tmpl = rc_config.get(
2193 uri_tmpl = rc_config.get(
2194 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2194 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2195 else:
2195 else:
2196 uri_tmpl = rc_config.get(
2196 uri_tmpl = rc_config.get(
2197 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2197 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2198
2198
2199 request = get_current_request()
2199 request = get_current_request()
2200 return get_clone_url(request=request,
2200 return get_clone_url(request=request,
2201 uri_tmpl=uri_tmpl,
2201 uri_tmpl=uri_tmpl,
2202 repo_name=self.repo_name,
2202 repo_name=self.repo_name,
2203 repo_id=self.repo_id, **override)
2203 repo_id=self.repo_id, **override)
2204
2204
2205 def set_state(self, state):
2205 def set_state(self, state):
2206 self.repo_state = state
2206 self.repo_state = state
2207 Session().add(self)
2207 Session().add(self)
2208 #==========================================================================
2208 #==========================================================================
2209 # SCM PROPERTIES
2209 # SCM PROPERTIES
2210 #==========================================================================
2210 #==========================================================================
2211
2211
2212 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2212 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2213 return get_commit_safe(
2213 return get_commit_safe(
2214 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2214 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2215
2215
2216 def get_changeset(self, rev=None, pre_load=None):
2216 def get_changeset(self, rev=None, pre_load=None):
2217 warnings.warn("Use get_commit", DeprecationWarning)
2217 warnings.warn("Use get_commit", DeprecationWarning)
2218 commit_id = None
2218 commit_id = None
2219 commit_idx = None
2219 commit_idx = None
2220 if isinstance(rev, basestring):
2220 if isinstance(rev, basestring):
2221 commit_id = rev
2221 commit_id = rev
2222 else:
2222 else:
2223 commit_idx = rev
2223 commit_idx = rev
2224 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2224 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2225 pre_load=pre_load)
2225 pre_load=pre_load)
2226
2226
2227 def get_landing_commit(self):
2227 def get_landing_commit(self):
2228 """
2228 """
2229 Returns landing commit, or if that doesn't exist returns the tip
2229 Returns landing commit, or if that doesn't exist returns the tip
2230 """
2230 """
2231 _rev_type, _rev = self.landing_rev
2231 _rev_type, _rev = self.landing_rev
2232 commit = self.get_commit(_rev)
2232 commit = self.get_commit(_rev)
2233 if isinstance(commit, EmptyCommit):
2233 if isinstance(commit, EmptyCommit):
2234 return self.get_commit()
2234 return self.get_commit()
2235 return commit
2235 return commit
2236
2236
2237 def update_commit_cache(self, cs_cache=None, config=None):
2237 def update_commit_cache(self, cs_cache=None, config=None):
2238 """
2238 """
2239 Update cache of last changeset for repository, keys should be::
2239 Update cache of last changeset for repository, keys should be::
2240
2240
2241 short_id
2241 short_id
2242 raw_id
2242 raw_id
2243 revision
2243 revision
2244 parents
2244 parents
2245 message
2245 message
2246 date
2246 date
2247 author
2247 author
2248
2248
2249 :param cs_cache:
2249 :param cs_cache:
2250 """
2250 """
2251 from rhodecode.lib.vcs.backends.base import BaseChangeset
2251 from rhodecode.lib.vcs.backends.base import BaseChangeset
2252 if cs_cache is None:
2252 if cs_cache is None:
2253 # use no-cache version here
2253 # use no-cache version here
2254 scm_repo = self.scm_instance(cache=False, config=config)
2254 scm_repo = self.scm_instance(cache=False, config=config)
2255
2255
2256 empty = scm_repo.is_empty()
2256 empty = scm_repo.is_empty()
2257 if not empty:
2257 if not empty:
2258 cs_cache = scm_repo.get_commit(
2258 cs_cache = scm_repo.get_commit(
2259 pre_load=["author", "date", "message", "parents"])
2259 pre_load=["author", "date", "message", "parents"])
2260 else:
2260 else:
2261 cs_cache = EmptyCommit()
2261 cs_cache = EmptyCommit()
2262
2262
2263 if isinstance(cs_cache, BaseChangeset):
2263 if isinstance(cs_cache, BaseChangeset):
2264 cs_cache = cs_cache.__json__()
2264 cs_cache = cs_cache.__json__()
2265
2265
2266 def is_outdated(new_cs_cache):
2266 def is_outdated(new_cs_cache):
2267 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2267 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2268 new_cs_cache['revision'] != self.changeset_cache['revision']):
2268 new_cs_cache['revision'] != self.changeset_cache['revision']):
2269 return True
2269 return True
2270 return False
2270 return False
2271
2271
2272 # check if we have maybe already latest cached revision
2272 # check if we have maybe already latest cached revision
2273 if is_outdated(cs_cache) or not self.changeset_cache:
2273 if is_outdated(cs_cache) or not self.changeset_cache:
2274 _default = datetime.datetime.utcnow()
2274 _default = datetime.datetime.utcnow()
2275 last_change = cs_cache.get('date') or _default
2275 last_change = cs_cache.get('date') or _default
2276 if self.updated_on and self.updated_on > last_change:
2276 if self.updated_on and self.updated_on > last_change:
2277 # we check if last update is newer than the new value
2277 # we check if last update is newer than the new value
2278 # if yes, we use the current timestamp instead. Imagine you get
2278 # if yes, we use the current timestamp instead. Imagine you get
2279 # old commit pushed 1y ago, we'd set last update 1y to ago.
2279 # old commit pushed 1y ago, we'd set last update 1y to ago.
2280 last_change = _default
2280 last_change = _default
2281 log.debug('updated repo %s with new cs cache %s',
2281 log.debug('updated repo %s with new cs cache %s',
2282 self.repo_name, cs_cache)
2282 self.repo_name, cs_cache)
2283 self.updated_on = last_change
2283 self.updated_on = last_change
2284 self.changeset_cache = cs_cache
2284 self.changeset_cache = cs_cache
2285 Session().add(self)
2285 Session().add(self)
2286 Session().commit()
2286 Session().commit()
2287 else:
2287 else:
2288 log.debug('Skipping update_commit_cache for repo:`%s` '
2288 log.debug('Skipping update_commit_cache for repo:`%s` '
2289 'commit already with latest changes', self.repo_name)
2289 'commit already with latest changes', self.repo_name)
2290
2290
2291 @property
2291 @property
2292 def tip(self):
2292 def tip(self):
2293 return self.get_commit('tip')
2293 return self.get_commit('tip')
2294
2294
2295 @property
2295 @property
2296 def author(self):
2296 def author(self):
2297 return self.tip.author
2297 return self.tip.author
2298
2298
2299 @property
2299 @property
2300 def last_change(self):
2300 def last_change(self):
2301 return self.scm_instance().last_change
2301 return self.scm_instance().last_change
2302
2302
2303 def get_comments(self, revisions=None):
2303 def get_comments(self, revisions=None):
2304 """
2304 """
2305 Returns comments for this repository grouped by revisions
2305 Returns comments for this repository grouped by revisions
2306
2306
2307 :param revisions: filter query by revisions only
2307 :param revisions: filter query by revisions only
2308 """
2308 """
2309 cmts = ChangesetComment.query()\
2309 cmts = ChangesetComment.query()\
2310 .filter(ChangesetComment.repo == self)
2310 .filter(ChangesetComment.repo == self)
2311 if revisions:
2311 if revisions:
2312 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2312 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2313 grouped = collections.defaultdict(list)
2313 grouped = collections.defaultdict(list)
2314 for cmt in cmts.all():
2314 for cmt in cmts.all():
2315 grouped[cmt.revision].append(cmt)
2315 grouped[cmt.revision].append(cmt)
2316 return grouped
2316 return grouped
2317
2317
2318 def statuses(self, revisions=None):
2318 def statuses(self, revisions=None):
2319 """
2319 """
2320 Returns statuses for this repository
2320 Returns statuses for this repository
2321
2321
2322 :param revisions: list of revisions to get statuses for
2322 :param revisions: list of revisions to get statuses for
2323 """
2323 """
2324 statuses = ChangesetStatus.query()\
2324 statuses = ChangesetStatus.query()\
2325 .filter(ChangesetStatus.repo == self)\
2325 .filter(ChangesetStatus.repo == self)\
2326 .filter(ChangesetStatus.version == 0)
2326 .filter(ChangesetStatus.version == 0)
2327
2327
2328 if revisions:
2328 if revisions:
2329 # Try doing the filtering in chunks to avoid hitting limits
2329 # Try doing the filtering in chunks to avoid hitting limits
2330 size = 500
2330 size = 500
2331 status_results = []
2331 status_results = []
2332 for chunk in xrange(0, len(revisions), size):
2332 for chunk in xrange(0, len(revisions), size):
2333 status_results += statuses.filter(
2333 status_results += statuses.filter(
2334 ChangesetStatus.revision.in_(
2334 ChangesetStatus.revision.in_(
2335 revisions[chunk: chunk+size])
2335 revisions[chunk: chunk+size])
2336 ).all()
2336 ).all()
2337 else:
2337 else:
2338 status_results = statuses.all()
2338 status_results = statuses.all()
2339
2339
2340 grouped = {}
2340 grouped = {}
2341
2341
2342 # maybe we have open new pullrequest without a status?
2342 # maybe we have open new pullrequest without a status?
2343 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2343 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2344 status_lbl = ChangesetStatus.get_status_lbl(stat)
2344 status_lbl = ChangesetStatus.get_status_lbl(stat)
2345 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2345 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2346 for rev in pr.revisions:
2346 for rev in pr.revisions:
2347 pr_id = pr.pull_request_id
2347 pr_id = pr.pull_request_id
2348 pr_repo = pr.target_repo.repo_name
2348 pr_repo = pr.target_repo.repo_name
2349 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2349 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2350
2350
2351 for stat in status_results:
2351 for stat in status_results:
2352 pr_id = pr_repo = None
2352 pr_id = pr_repo = None
2353 if stat.pull_request:
2353 if stat.pull_request:
2354 pr_id = stat.pull_request.pull_request_id
2354 pr_id = stat.pull_request.pull_request_id
2355 pr_repo = stat.pull_request.target_repo.repo_name
2355 pr_repo = stat.pull_request.target_repo.repo_name
2356 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2356 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2357 pr_id, pr_repo]
2357 pr_id, pr_repo]
2358 return grouped
2358 return grouped
2359
2359
2360 # ==========================================================================
2360 # ==========================================================================
2361 # SCM CACHE INSTANCE
2361 # SCM CACHE INSTANCE
2362 # ==========================================================================
2362 # ==========================================================================
2363
2363
2364 def scm_instance(self, **kwargs):
2364 def scm_instance(self, **kwargs):
2365 import rhodecode
2365 import rhodecode
2366
2366
2367 # Passing a config will not hit the cache currently only used
2367 # Passing a config will not hit the cache currently only used
2368 # for repo2dbmapper
2368 # for repo2dbmapper
2369 config = kwargs.pop('config', None)
2369 config = kwargs.pop('config', None)
2370 cache = kwargs.pop('cache', None)
2370 cache = kwargs.pop('cache', None)
2371 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2371 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2372 # if cache is NOT defined use default global, else we have a full
2372 # if cache is NOT defined use default global, else we have a full
2373 # control over cache behaviour
2373 # control over cache behaviour
2374 if cache is None and full_cache and not config:
2374 if cache is None and full_cache and not config:
2375 return self._get_instance_cached()
2375 return self._get_instance_cached()
2376 return self._get_instance(cache=bool(cache), config=config)
2376 return self._get_instance(cache=bool(cache), config=config)
2377
2377
2378 def _get_instance_cached(self):
2378 def _get_instance_cached(self):
2379 from rhodecode.lib import rc_cache
2379 from rhodecode.lib import rc_cache
2380
2380
2381 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2381 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2382 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2382 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2383 repo_id=self.repo_id)
2383 repo_id=self.repo_id)
2384 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2384 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2385
2385
2386 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2386 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2387 def get_instance_cached(repo_id, context_id):
2387 def get_instance_cached(repo_id, context_id):
2388 return self._get_instance()
2388 return self._get_instance()
2389
2389
2390 # we must use thread scoped cache here,
2390 # we must use thread scoped cache here,
2391 # because each thread of gevent needs it's own not shared connection and cache
2391 # because each thread of gevent needs it's own not shared connection and cache
2392 # we also alter `args` so the cache key is individual for every green thread.
2392 # we also alter `args` so the cache key is individual for every green thread.
2393 inv_context_manager = rc_cache.InvalidationContext(
2393 inv_context_manager = rc_cache.InvalidationContext(
2394 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2394 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2395 thread_scoped=True)
2395 thread_scoped=True)
2396 with inv_context_manager as invalidation_context:
2396 with inv_context_manager as invalidation_context:
2397 args = (self.repo_id, inv_context_manager.cache_key)
2397 args = (self.repo_id, inv_context_manager.cache_key)
2398 # re-compute and store cache if we get invalidate signal
2398 # re-compute and store cache if we get invalidate signal
2399 if invalidation_context.should_invalidate():
2399 if invalidation_context.should_invalidate():
2400 instance = get_instance_cached.refresh(*args)
2400 instance = get_instance_cached.refresh(*args)
2401 else:
2401 else:
2402 instance = get_instance_cached(*args)
2402 instance = get_instance_cached(*args)
2403
2403
2404 log.debug(
2404 log.debug(
2405 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2405 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2406 return instance
2406 return instance
2407
2407
2408 def _get_instance(self, cache=True, config=None):
2408 def _get_instance(self, cache=True, config=None):
2409 config = config or self._config
2409 config = config or self._config
2410 custom_wire = {
2410 custom_wire = {
2411 'cache': cache # controls the vcs.remote cache
2411 'cache': cache # controls the vcs.remote cache
2412 }
2412 }
2413 repo = get_vcs_instance(
2413 repo = get_vcs_instance(
2414 repo_path=safe_str(self.repo_full_path),
2414 repo_path=safe_str(self.repo_full_path),
2415 config=config,
2415 config=config,
2416 with_wire=custom_wire,
2416 with_wire=custom_wire,
2417 create=False,
2417 create=False,
2418 _vcs_alias=self.repo_type)
2418 _vcs_alias=self.repo_type)
2419
2419
2420 return repo
2420 return repo
2421
2421
2422 def __json__(self):
2422 def __json__(self):
2423 return {'landing_rev': self.landing_rev}
2423 return {'landing_rev': self.landing_rev}
2424
2424
2425 def get_dict(self):
2425 def get_dict(self):
2426
2426
2427 # Since we transformed `repo_name` to a hybrid property, we need to
2427 # Since we transformed `repo_name` to a hybrid property, we need to
2428 # keep compatibility with the code which uses `repo_name` field.
2428 # keep compatibility with the code which uses `repo_name` field.
2429
2429
2430 result = super(Repository, self).get_dict()
2430 result = super(Repository, self).get_dict()
2431 result['repo_name'] = result.pop('_repo_name', None)
2431 result['repo_name'] = result.pop('_repo_name', None)
2432 return result
2432 return result
2433
2433
2434
2434
2435 class RepoGroup(Base, BaseModel):
2435 class RepoGroup(Base, BaseModel):
2436 __tablename__ = 'groups'
2436 __tablename__ = 'groups'
2437 __table_args__ = (
2437 __table_args__ = (
2438 UniqueConstraint('group_name', 'group_parent_id'),
2438 UniqueConstraint('group_name', 'group_parent_id'),
2439 CheckConstraint('group_id != group_parent_id'),
2439 CheckConstraint('group_id != group_parent_id'),
2440 base_table_args,
2440 base_table_args,
2441 )
2441 )
2442 __mapper_args__ = {'order_by': 'group_name'}
2442 __mapper_args__ = {'order_by': 'group_name'}
2443
2443
2444 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2444 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2445
2445
2446 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2446 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2447 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2447 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2448 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2448 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2449 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2449 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2450 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2450 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2451 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2451 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2452 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2452 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2453 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2453 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2454 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2454 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2455
2455
2456 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2456 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2457 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2457 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2458 parent_group = relationship('RepoGroup', remote_side=group_id)
2458 parent_group = relationship('RepoGroup', remote_side=group_id)
2459 user = relationship('User')
2459 user = relationship('User')
2460 integrations = relationship('Integration',
2460 integrations = relationship('Integration',
2461 cascade="all, delete, delete-orphan")
2461 cascade="all, delete, delete-orphan")
2462
2462
2463 def __init__(self, group_name='', parent_group=None):
2463 def __init__(self, group_name='', parent_group=None):
2464 self.group_name = group_name
2464 self.group_name = group_name
2465 self.parent_group = parent_group
2465 self.parent_group = parent_group
2466
2466
2467 def __unicode__(self):
2467 def __unicode__(self):
2468 return u"<%s('id:%s:%s')>" % (
2468 return u"<%s('id:%s:%s')>" % (
2469 self.__class__.__name__, self.group_id, self.group_name)
2469 self.__class__.__name__, self.group_id, self.group_name)
2470
2470
2471 @hybrid_property
2471 @hybrid_property
2472 def description_safe(self):
2472 def description_safe(self):
2473 from rhodecode.lib import helpers as h
2473 from rhodecode.lib import helpers as h
2474 return h.escape(self.group_description)
2474 return h.escape(self.group_description)
2475
2475
2476 @classmethod
2476 @classmethod
2477 def _generate_choice(cls, repo_group):
2477 def _generate_choice(cls, repo_group):
2478 from webhelpers.html import literal as _literal
2478 from webhelpers.html import literal as _literal
2479 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2479 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2480 return repo_group.group_id, _name(repo_group.full_path_splitted)
2480 return repo_group.group_id, _name(repo_group.full_path_splitted)
2481
2481
2482 @classmethod
2482 @classmethod
2483 def groups_choices(cls, groups=None, show_empty_group=True):
2483 def groups_choices(cls, groups=None, show_empty_group=True):
2484 if not groups:
2484 if not groups:
2485 groups = cls.query().all()
2485 groups = cls.query().all()
2486
2486
2487 repo_groups = []
2487 repo_groups = []
2488 if show_empty_group:
2488 if show_empty_group:
2489 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2489 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2490
2490
2491 repo_groups.extend([cls._generate_choice(x) for x in groups])
2491 repo_groups.extend([cls._generate_choice(x) for x in groups])
2492
2492
2493 repo_groups = sorted(
2493 repo_groups = sorted(
2494 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2494 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2495 return repo_groups
2495 return repo_groups
2496
2496
2497 @classmethod
2497 @classmethod
2498 def url_sep(cls):
2498 def url_sep(cls):
2499 return URL_SEP
2499 return URL_SEP
2500
2500
2501 @classmethod
2501 @classmethod
2502 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2502 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2503 if case_insensitive:
2503 if case_insensitive:
2504 gr = cls.query().filter(func.lower(cls.group_name)
2504 gr = cls.query().filter(func.lower(cls.group_name)
2505 == func.lower(group_name))
2505 == func.lower(group_name))
2506 else:
2506 else:
2507 gr = cls.query().filter(cls.group_name == group_name)
2507 gr = cls.query().filter(cls.group_name == group_name)
2508 if cache:
2508 if cache:
2509 name_key = _hash_key(group_name)
2509 name_key = _hash_key(group_name)
2510 gr = gr.options(
2510 gr = gr.options(
2511 FromCache("sql_cache_short", "get_group_%s" % name_key))
2511 FromCache("sql_cache_short", "get_group_%s" % name_key))
2512 return gr.scalar()
2512 return gr.scalar()
2513
2513
2514 @classmethod
2514 @classmethod
2515 def get_user_personal_repo_group(cls, user_id):
2515 def get_user_personal_repo_group(cls, user_id):
2516 user = User.get(user_id)
2516 user = User.get(user_id)
2517 if user.username == User.DEFAULT_USER:
2517 if user.username == User.DEFAULT_USER:
2518 return None
2518 return None
2519
2519
2520 return cls.query()\
2520 return cls.query()\
2521 .filter(cls.personal == true()) \
2521 .filter(cls.personal == true()) \
2522 .filter(cls.user == user) \
2522 .filter(cls.user == user) \
2523 .order_by(cls.group_id.asc()) \
2523 .order_by(cls.group_id.asc()) \
2524 .first()
2524 .first()
2525
2525
2526 @classmethod
2526 @classmethod
2527 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2527 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2528 case_insensitive=True):
2528 case_insensitive=True):
2529 q = RepoGroup.query()
2529 q = RepoGroup.query()
2530
2530
2531 if not isinstance(user_id, Optional):
2531 if not isinstance(user_id, Optional):
2532 q = q.filter(RepoGroup.user_id == user_id)
2532 q = q.filter(RepoGroup.user_id == user_id)
2533
2533
2534 if not isinstance(group_id, Optional):
2534 if not isinstance(group_id, Optional):
2535 q = q.filter(RepoGroup.group_parent_id == group_id)
2535 q = q.filter(RepoGroup.group_parent_id == group_id)
2536
2536
2537 if case_insensitive:
2537 if case_insensitive:
2538 q = q.order_by(func.lower(RepoGroup.group_name))
2538 q = q.order_by(func.lower(RepoGroup.group_name))
2539 else:
2539 else:
2540 q = q.order_by(RepoGroup.group_name)
2540 q = q.order_by(RepoGroup.group_name)
2541 return q.all()
2541 return q.all()
2542
2542
2543 @property
2543 @property
2544 def parents(self):
2544 def parents(self):
2545 parents_recursion_limit = 10
2545 parents_recursion_limit = 10
2546 groups = []
2546 groups = []
2547 if self.parent_group is None:
2547 if self.parent_group is None:
2548 return groups
2548 return groups
2549 cur_gr = self.parent_group
2549 cur_gr = self.parent_group
2550 groups.insert(0, cur_gr)
2550 groups.insert(0, cur_gr)
2551 cnt = 0
2551 cnt = 0
2552 while 1:
2552 while 1:
2553 cnt += 1
2553 cnt += 1
2554 gr = getattr(cur_gr, 'parent_group', None)
2554 gr = getattr(cur_gr, 'parent_group', None)
2555 cur_gr = cur_gr.parent_group
2555 cur_gr = cur_gr.parent_group
2556 if gr is None:
2556 if gr is None:
2557 break
2557 break
2558 if cnt == parents_recursion_limit:
2558 if cnt == parents_recursion_limit:
2559 # this will prevent accidental infinit loops
2559 # this will prevent accidental infinit loops
2560 log.error('more than %s parents found for group %s, stopping '
2560 log.error('more than %s parents found for group %s, stopping '
2561 'recursive parent fetching', parents_recursion_limit, self)
2561 'recursive parent fetching', parents_recursion_limit, self)
2562 break
2562 break
2563
2563
2564 groups.insert(0, gr)
2564 groups.insert(0, gr)
2565 return groups
2565 return groups
2566
2566
2567 @property
2567 @property
2568 def last_db_change(self):
2568 def last_db_change(self):
2569 return self.updated_on
2569 return self.updated_on
2570
2570
2571 @property
2571 @property
2572 def children(self):
2572 def children(self):
2573 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2573 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2574
2574
2575 @property
2575 @property
2576 def name(self):
2576 def name(self):
2577 return self.group_name.split(RepoGroup.url_sep())[-1]
2577 return self.group_name.split(RepoGroup.url_sep())[-1]
2578
2578
2579 @property
2579 @property
2580 def full_path(self):
2580 def full_path(self):
2581 return self.group_name
2581 return self.group_name
2582
2582
2583 @property
2583 @property
2584 def full_path_splitted(self):
2584 def full_path_splitted(self):
2585 return self.group_name.split(RepoGroup.url_sep())
2585 return self.group_name.split(RepoGroup.url_sep())
2586
2586
2587 @property
2587 @property
2588 def repositories(self):
2588 def repositories(self):
2589 return Repository.query()\
2589 return Repository.query()\
2590 .filter(Repository.group == self)\
2590 .filter(Repository.group == self)\
2591 .order_by(Repository.repo_name)
2591 .order_by(Repository.repo_name)
2592
2592
2593 @property
2593 @property
2594 def repositories_recursive_count(self):
2594 def repositories_recursive_count(self):
2595 cnt = self.repositories.count()
2595 cnt = self.repositories.count()
2596
2596
2597 def children_count(group):
2597 def children_count(group):
2598 cnt = 0
2598 cnt = 0
2599 for child in group.children:
2599 for child in group.children:
2600 cnt += child.repositories.count()
2600 cnt += child.repositories.count()
2601 cnt += children_count(child)
2601 cnt += children_count(child)
2602 return cnt
2602 return cnt
2603
2603
2604 return cnt + children_count(self)
2604 return cnt + children_count(self)
2605
2605
2606 def _recursive_objects(self, include_repos=True):
2606 def _recursive_objects(self, include_repos=True):
2607 all_ = []
2607 all_ = []
2608
2608
2609 def _get_members(root_gr):
2609 def _get_members(root_gr):
2610 if include_repos:
2610 if include_repos:
2611 for r in root_gr.repositories:
2611 for r in root_gr.repositories:
2612 all_.append(r)
2612 all_.append(r)
2613 childs = root_gr.children.all()
2613 childs = root_gr.children.all()
2614 if childs:
2614 if childs:
2615 for gr in childs:
2615 for gr in childs:
2616 all_.append(gr)
2616 all_.append(gr)
2617 _get_members(gr)
2617 _get_members(gr)
2618
2618
2619 _get_members(self)
2619 _get_members(self)
2620 return [self] + all_
2620 return [self] + all_
2621
2621
2622 def recursive_groups_and_repos(self):
2622 def recursive_groups_and_repos(self):
2623 """
2623 """
2624 Recursive return all groups, with repositories in those groups
2624 Recursive return all groups, with repositories in those groups
2625 """
2625 """
2626 return self._recursive_objects()
2626 return self._recursive_objects()
2627
2627
2628 def recursive_groups(self):
2628 def recursive_groups(self):
2629 """
2629 """
2630 Returns all children groups for this group including children of children
2630 Returns all children groups for this group including children of children
2631 """
2631 """
2632 return self._recursive_objects(include_repos=False)
2632 return self._recursive_objects(include_repos=False)
2633
2633
2634 def get_new_name(self, group_name):
2634 def get_new_name(self, group_name):
2635 """
2635 """
2636 returns new full group name based on parent and new name
2636 returns new full group name based on parent and new name
2637
2637
2638 :param group_name:
2638 :param group_name:
2639 """
2639 """
2640 path_prefix = (self.parent_group.full_path_splitted if
2640 path_prefix = (self.parent_group.full_path_splitted if
2641 self.parent_group else [])
2641 self.parent_group else [])
2642 return RepoGroup.url_sep().join(path_prefix + [group_name])
2642 return RepoGroup.url_sep().join(path_prefix + [group_name])
2643
2643
2644 def permissions(self, with_admins=True, with_owner=True):
2644 def permissions(self, with_admins=True, with_owner=True):
2645 """
2645 """
2646 Permissions for repository groups
2646 Permissions for repository groups
2647 """
2647 """
2648 _admin_perm = 'group.admin'
2648 _admin_perm = 'group.admin'
2649
2649
2650 owner_row = []
2650 owner_row = []
2651 if with_owner:
2651 if with_owner:
2652 usr = AttributeDict(self.user.get_dict())
2652 usr = AttributeDict(self.user.get_dict())
2653 usr.owner_row = True
2653 usr.owner_row = True
2654 usr.permission = _admin_perm
2654 usr.permission = _admin_perm
2655 owner_row.append(usr)
2655 owner_row.append(usr)
2656
2656
2657 super_admin_ids = []
2657 super_admin_ids = []
2658 super_admin_rows = []
2658 super_admin_rows = []
2659 if with_admins:
2659 if with_admins:
2660 for usr in User.get_all_super_admins():
2660 for usr in User.get_all_super_admins():
2661 super_admin_ids.append(usr.user_id)
2661 super_admin_ids.append(usr.user_id)
2662 # if this admin is also owner, don't double the record
2662 # if this admin is also owner, don't double the record
2663 if usr.user_id == owner_row[0].user_id:
2663 if usr.user_id == owner_row[0].user_id:
2664 owner_row[0].admin_row = True
2664 owner_row[0].admin_row = True
2665 else:
2665 else:
2666 usr = AttributeDict(usr.get_dict())
2666 usr = AttributeDict(usr.get_dict())
2667 usr.admin_row = True
2667 usr.admin_row = True
2668 usr.permission = _admin_perm
2668 usr.permission = _admin_perm
2669 super_admin_rows.append(usr)
2669 super_admin_rows.append(usr)
2670
2670
2671 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2671 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2672 q = q.options(joinedload(UserRepoGroupToPerm.group),
2672 q = q.options(joinedload(UserRepoGroupToPerm.group),
2673 joinedload(UserRepoGroupToPerm.user),
2673 joinedload(UserRepoGroupToPerm.user),
2674 joinedload(UserRepoGroupToPerm.permission),)
2674 joinedload(UserRepoGroupToPerm.permission),)
2675
2675
2676 # get owners and admins and permissions. We do a trick of re-writing
2676 # get owners and admins and permissions. We do a trick of re-writing
2677 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2677 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2678 # has a global reference and changing one object propagates to all
2678 # has a global reference and changing one object propagates to all
2679 # others. This means if admin is also an owner admin_row that change
2679 # others. This means if admin is also an owner admin_row that change
2680 # would propagate to both objects
2680 # would propagate to both objects
2681 perm_rows = []
2681 perm_rows = []
2682 for _usr in q.all():
2682 for _usr in q.all():
2683 usr = AttributeDict(_usr.user.get_dict())
2683 usr = AttributeDict(_usr.user.get_dict())
2684 # if this user is also owner/admin, mark as duplicate record
2684 # if this user is also owner/admin, mark as duplicate record
2685 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2685 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2686 usr.duplicate_perm = True
2686 usr.duplicate_perm = True
2687 usr.permission = _usr.permission.permission_name
2687 usr.permission = _usr.permission.permission_name
2688 perm_rows.append(usr)
2688 perm_rows.append(usr)
2689
2689
2690 # filter the perm rows by 'default' first and then sort them by
2690 # filter the perm rows by 'default' first and then sort them by
2691 # admin,write,read,none permissions sorted again alphabetically in
2691 # admin,write,read,none permissions sorted again alphabetically in
2692 # each group
2692 # each group
2693 perm_rows = sorted(perm_rows, key=display_user_sort)
2693 perm_rows = sorted(perm_rows, key=display_user_sort)
2694
2694
2695 return super_admin_rows + owner_row + perm_rows
2695 return super_admin_rows + owner_row + perm_rows
2696
2696
2697 def permission_user_groups(self):
2697 def permission_user_groups(self):
2698 q = UserGroupRepoGroupToPerm.query().filter(
2698 q = UserGroupRepoGroupToPerm.query().filter(
2699 UserGroupRepoGroupToPerm.group == self)
2699 UserGroupRepoGroupToPerm.group == self)
2700 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2700 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2701 joinedload(UserGroupRepoGroupToPerm.users_group),
2701 joinedload(UserGroupRepoGroupToPerm.users_group),
2702 joinedload(UserGroupRepoGroupToPerm.permission),)
2702 joinedload(UserGroupRepoGroupToPerm.permission),)
2703
2703
2704 perm_rows = []
2704 perm_rows = []
2705 for _user_group in q.all():
2705 for _user_group in q.all():
2706 usr = AttributeDict(_user_group.users_group.get_dict())
2706 usr = AttributeDict(_user_group.users_group.get_dict())
2707 usr.permission = _user_group.permission.permission_name
2707 usr.permission = _user_group.permission.permission_name
2708 perm_rows.append(usr)
2708 perm_rows.append(usr)
2709
2709
2710 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2710 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2711 return perm_rows
2711 return perm_rows
2712
2712
2713 def get_api_data(self):
2713 def get_api_data(self):
2714 """
2714 """
2715 Common function for generating api data
2715 Common function for generating api data
2716
2716
2717 """
2717 """
2718 group = self
2718 group = self
2719 data = {
2719 data = {
2720 'group_id': group.group_id,
2720 'group_id': group.group_id,
2721 'group_name': group.group_name,
2721 'group_name': group.group_name,
2722 'group_description': group.description_safe,
2722 'group_description': group.description_safe,
2723 'parent_group': group.parent_group.group_name if group.parent_group else None,
2723 'parent_group': group.parent_group.group_name if group.parent_group else None,
2724 'repositories': [x.repo_name for x in group.repositories],
2724 'repositories': [x.repo_name for x in group.repositories],
2725 'owner': group.user.username,
2725 'owner': group.user.username,
2726 }
2726 }
2727 return data
2727 return data
2728
2728
2729
2729
2730 class Permission(Base, BaseModel):
2730 class Permission(Base, BaseModel):
2731 __tablename__ = 'permissions'
2731 __tablename__ = 'permissions'
2732 __table_args__ = (
2732 __table_args__ = (
2733 Index('p_perm_name_idx', 'permission_name'),
2733 Index('p_perm_name_idx', 'permission_name'),
2734 base_table_args,
2734 base_table_args,
2735 )
2735 )
2736
2736
2737 PERMS = [
2737 PERMS = [
2738 ('hg.admin', _('RhodeCode Super Administrator')),
2738 ('hg.admin', _('RhodeCode Super Administrator')),
2739
2739
2740 ('repository.none', _('Repository no access')),
2740 ('repository.none', _('Repository no access')),
2741 ('repository.read', _('Repository read access')),
2741 ('repository.read', _('Repository read access')),
2742 ('repository.write', _('Repository write access')),
2742 ('repository.write', _('Repository write access')),
2743 ('repository.admin', _('Repository admin access')),
2743 ('repository.admin', _('Repository admin access')),
2744
2744
2745 ('group.none', _('Repository group no access')),
2745 ('group.none', _('Repository group no access')),
2746 ('group.read', _('Repository group read access')),
2746 ('group.read', _('Repository group read access')),
2747 ('group.write', _('Repository group write access')),
2747 ('group.write', _('Repository group write access')),
2748 ('group.admin', _('Repository group admin access')),
2748 ('group.admin', _('Repository group admin access')),
2749
2749
2750 ('usergroup.none', _('User group no access')),
2750 ('usergroup.none', _('User group no access')),
2751 ('usergroup.read', _('User group read access')),
2751 ('usergroup.read', _('User group read access')),
2752 ('usergroup.write', _('User group write access')),
2752 ('usergroup.write', _('User group write access')),
2753 ('usergroup.admin', _('User group admin access')),
2753 ('usergroup.admin', _('User group admin access')),
2754
2754
2755 ('branch.none', _('Branch no permissions')),
2755 ('branch.none', _('Branch no permissions')),
2756 ('branch.merge', _('Branch access by web merge')),
2756 ('branch.merge', _('Branch access by web merge')),
2757 ('branch.push', _('Branch access by push')),
2757 ('branch.push', _('Branch access by push')),
2758 ('branch.push_force', _('Branch access by push with force')),
2758 ('branch.push_force', _('Branch access by push with force')),
2759
2759
2760 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2760 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2761 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2761 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2762
2762
2763 ('hg.usergroup.create.false', _('User Group creation disabled')),
2763 ('hg.usergroup.create.false', _('User Group creation disabled')),
2764 ('hg.usergroup.create.true', _('User Group creation enabled')),
2764 ('hg.usergroup.create.true', _('User Group creation enabled')),
2765
2765
2766 ('hg.create.none', _('Repository creation disabled')),
2766 ('hg.create.none', _('Repository creation disabled')),
2767 ('hg.create.repository', _('Repository creation enabled')),
2767 ('hg.create.repository', _('Repository creation enabled')),
2768 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2768 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2769 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2769 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2770
2770
2771 ('hg.fork.none', _('Repository forking disabled')),
2771 ('hg.fork.none', _('Repository forking disabled')),
2772 ('hg.fork.repository', _('Repository forking enabled')),
2772 ('hg.fork.repository', _('Repository forking enabled')),
2773
2773
2774 ('hg.register.none', _('Registration disabled')),
2774 ('hg.register.none', _('Registration disabled')),
2775 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2775 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2776 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2776 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2777
2777
2778 ('hg.password_reset.enabled', _('Password reset enabled')),
2778 ('hg.password_reset.enabled', _('Password reset enabled')),
2779 ('hg.password_reset.hidden', _('Password reset hidden')),
2779 ('hg.password_reset.hidden', _('Password reset hidden')),
2780 ('hg.password_reset.disabled', _('Password reset disabled')),
2780 ('hg.password_reset.disabled', _('Password reset disabled')),
2781
2781
2782 ('hg.extern_activate.manual', _('Manual activation of external account')),
2782 ('hg.extern_activate.manual', _('Manual activation of external account')),
2783 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2783 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2784
2784
2785 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2785 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2786 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2786 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2787 ]
2787 ]
2788
2788
2789 # definition of system default permissions for DEFAULT user, created on
2789 # definition of system default permissions for DEFAULT user, created on
2790 # system setup
2790 # system setup
2791 DEFAULT_USER_PERMISSIONS = [
2791 DEFAULT_USER_PERMISSIONS = [
2792 # object perms
2792 # object perms
2793 'repository.read',
2793 'repository.read',
2794 'group.read',
2794 'group.read',
2795 'usergroup.read',
2795 'usergroup.read',
2796 # branch, for backward compat we need same value as before so forced pushed
2796 # branch, for backward compat we need same value as before so forced pushed
2797 'branch.push_force',
2797 'branch.push_force',
2798 # global
2798 # global
2799 'hg.create.repository',
2799 'hg.create.repository',
2800 'hg.repogroup.create.false',
2800 'hg.repogroup.create.false',
2801 'hg.usergroup.create.false',
2801 'hg.usergroup.create.false',
2802 'hg.create.write_on_repogroup.true',
2802 'hg.create.write_on_repogroup.true',
2803 'hg.fork.repository',
2803 'hg.fork.repository',
2804 'hg.register.manual_activate',
2804 'hg.register.manual_activate',
2805 'hg.password_reset.enabled',
2805 'hg.password_reset.enabled',
2806 'hg.extern_activate.auto',
2806 'hg.extern_activate.auto',
2807 'hg.inherit_default_perms.true',
2807 'hg.inherit_default_perms.true',
2808 ]
2808 ]
2809
2809
2810 # defines which permissions are more important higher the more important
2810 # defines which permissions are more important higher the more important
2811 # Weight defines which permissions are more important.
2811 # Weight defines which permissions are more important.
2812 # The higher number the more important.
2812 # The higher number the more important.
2813 PERM_WEIGHTS = {
2813 PERM_WEIGHTS = {
2814 'repository.none': 0,
2814 'repository.none': 0,
2815 'repository.read': 1,
2815 'repository.read': 1,
2816 'repository.write': 3,
2816 'repository.write': 3,
2817 'repository.admin': 4,
2817 'repository.admin': 4,
2818
2818
2819 'group.none': 0,
2819 'group.none': 0,
2820 'group.read': 1,
2820 'group.read': 1,
2821 'group.write': 3,
2821 'group.write': 3,
2822 'group.admin': 4,
2822 'group.admin': 4,
2823
2823
2824 'usergroup.none': 0,
2824 'usergroup.none': 0,
2825 'usergroup.read': 1,
2825 'usergroup.read': 1,
2826 'usergroup.write': 3,
2826 'usergroup.write': 3,
2827 'usergroup.admin': 4,
2827 'usergroup.admin': 4,
2828
2828
2829 'branch.none': 0,
2829 'branch.none': 0,
2830 'branch.merge': 1,
2830 'branch.merge': 1,
2831 'branch.push': 3,
2831 'branch.push': 3,
2832 'branch.push_force': 4,
2832 'branch.push_force': 4,
2833
2833
2834 'hg.repogroup.create.false': 0,
2834 'hg.repogroup.create.false': 0,
2835 'hg.repogroup.create.true': 1,
2835 'hg.repogroup.create.true': 1,
2836
2836
2837 'hg.usergroup.create.false': 0,
2837 'hg.usergroup.create.false': 0,
2838 'hg.usergroup.create.true': 1,
2838 'hg.usergroup.create.true': 1,
2839
2839
2840 'hg.fork.none': 0,
2840 'hg.fork.none': 0,
2841 'hg.fork.repository': 1,
2841 'hg.fork.repository': 1,
2842 'hg.create.none': 0,
2842 'hg.create.none': 0,
2843 'hg.create.repository': 1
2843 'hg.create.repository': 1
2844 }
2844 }
2845
2845
2846 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2846 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2847 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2847 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2848 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2848 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2849
2849
2850 def __unicode__(self):
2850 def __unicode__(self):
2851 return u"<%s('%s:%s')>" % (
2851 return u"<%s('%s:%s')>" % (
2852 self.__class__.__name__, self.permission_id, self.permission_name
2852 self.__class__.__name__, self.permission_id, self.permission_name
2853 )
2853 )
2854
2854
2855 @classmethod
2855 @classmethod
2856 def get_by_key(cls, key):
2856 def get_by_key(cls, key):
2857 return cls.query().filter(cls.permission_name == key).scalar()
2857 return cls.query().filter(cls.permission_name == key).scalar()
2858
2858
2859 @classmethod
2859 @classmethod
2860 def get_default_repo_perms(cls, user_id, repo_id=None):
2860 def get_default_repo_perms(cls, user_id, repo_id=None):
2861 q = Session().query(UserRepoToPerm, Repository, Permission)\
2861 q = Session().query(UserRepoToPerm, Repository, Permission)\
2862 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2862 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2863 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2863 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2864 .filter(UserRepoToPerm.user_id == user_id)
2864 .filter(UserRepoToPerm.user_id == user_id)
2865 if repo_id:
2865 if repo_id:
2866 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2866 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2867 return q.all()
2867 return q.all()
2868
2868
2869 @classmethod
2869 @classmethod
2870 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2870 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2871 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2871 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2872 .join(
2872 .join(
2873 Permission,
2873 Permission,
2874 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2874 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2875 .join(
2875 .join(
2876 UserRepoToPerm,
2876 UserRepoToPerm,
2877 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2877 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2878 .filter(UserRepoToPerm.user_id == user_id)
2878 .filter(UserRepoToPerm.user_id == user_id)
2879
2879
2880 if repo_id:
2880 if repo_id:
2881 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2881 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2882 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2882 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2883
2883
2884 @classmethod
2884 @classmethod
2885 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2885 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2886 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2886 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2887 .join(
2887 .join(
2888 Permission,
2888 Permission,
2889 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2889 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2890 .join(
2890 .join(
2891 Repository,
2891 Repository,
2892 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2892 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2893 .join(
2893 .join(
2894 UserGroup,
2894 UserGroup,
2895 UserGroupRepoToPerm.users_group_id ==
2895 UserGroupRepoToPerm.users_group_id ==
2896 UserGroup.users_group_id)\
2896 UserGroup.users_group_id)\
2897 .join(
2897 .join(
2898 UserGroupMember,
2898 UserGroupMember,
2899 UserGroupRepoToPerm.users_group_id ==
2899 UserGroupRepoToPerm.users_group_id ==
2900 UserGroupMember.users_group_id)\
2900 UserGroupMember.users_group_id)\
2901 .filter(
2901 .filter(
2902 UserGroupMember.user_id == user_id,
2902 UserGroupMember.user_id == user_id,
2903 UserGroup.users_group_active == true())
2903 UserGroup.users_group_active == true())
2904 if repo_id:
2904 if repo_id:
2905 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2905 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2906 return q.all()
2906 return q.all()
2907
2907
2908 @classmethod
2908 @classmethod
2909 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2909 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2910 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2910 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2911 .join(
2911 .join(
2912 Permission,
2912 Permission,
2913 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2913 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2914 .join(
2914 .join(
2915 UserGroupRepoToPerm,
2915 UserGroupRepoToPerm,
2916 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2916 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2917 .join(
2917 .join(
2918 UserGroup,
2918 UserGroup,
2919 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2919 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2920 .join(
2920 .join(
2921 UserGroupMember,
2921 UserGroupMember,
2922 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2922 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2923 .filter(
2923 .filter(
2924 UserGroupMember.user_id == user_id,
2924 UserGroupMember.user_id == user_id,
2925 UserGroup.users_group_active == true())
2925 UserGroup.users_group_active == true())
2926
2926
2927 if repo_id:
2927 if repo_id:
2928 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2928 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2929 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2929 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2930
2930
2931 @classmethod
2931 @classmethod
2932 def get_default_group_perms(cls, user_id, repo_group_id=None):
2932 def get_default_group_perms(cls, user_id, repo_group_id=None):
2933 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2933 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2934 .join(
2934 .join(
2935 Permission,
2935 Permission,
2936 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2936 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2937 .join(
2937 .join(
2938 RepoGroup,
2938 RepoGroup,
2939 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2939 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2940 .filter(UserRepoGroupToPerm.user_id == user_id)
2940 .filter(UserRepoGroupToPerm.user_id == user_id)
2941 if repo_group_id:
2941 if repo_group_id:
2942 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2942 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2943 return q.all()
2943 return q.all()
2944
2944
2945 @classmethod
2945 @classmethod
2946 def get_default_group_perms_from_user_group(
2946 def get_default_group_perms_from_user_group(
2947 cls, user_id, repo_group_id=None):
2947 cls, user_id, repo_group_id=None):
2948 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2948 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2949 .join(
2949 .join(
2950 Permission,
2950 Permission,
2951 UserGroupRepoGroupToPerm.permission_id ==
2951 UserGroupRepoGroupToPerm.permission_id ==
2952 Permission.permission_id)\
2952 Permission.permission_id)\
2953 .join(
2953 .join(
2954 RepoGroup,
2954 RepoGroup,
2955 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2955 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2956 .join(
2956 .join(
2957 UserGroup,
2957 UserGroup,
2958 UserGroupRepoGroupToPerm.users_group_id ==
2958 UserGroupRepoGroupToPerm.users_group_id ==
2959 UserGroup.users_group_id)\
2959 UserGroup.users_group_id)\
2960 .join(
2960 .join(
2961 UserGroupMember,
2961 UserGroupMember,
2962 UserGroupRepoGroupToPerm.users_group_id ==
2962 UserGroupRepoGroupToPerm.users_group_id ==
2963 UserGroupMember.users_group_id)\
2963 UserGroupMember.users_group_id)\
2964 .filter(
2964 .filter(
2965 UserGroupMember.user_id == user_id,
2965 UserGroupMember.user_id == user_id,
2966 UserGroup.users_group_active == true())
2966 UserGroup.users_group_active == true())
2967 if repo_group_id:
2967 if repo_group_id:
2968 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2968 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2969 return q.all()
2969 return q.all()
2970
2970
2971 @classmethod
2971 @classmethod
2972 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2972 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2973 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2973 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2974 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2974 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2975 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2975 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2976 .filter(UserUserGroupToPerm.user_id == user_id)
2976 .filter(UserUserGroupToPerm.user_id == user_id)
2977 if user_group_id:
2977 if user_group_id:
2978 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2978 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2979 return q.all()
2979 return q.all()
2980
2980
2981 @classmethod
2981 @classmethod
2982 def get_default_user_group_perms_from_user_group(
2982 def get_default_user_group_perms_from_user_group(
2983 cls, user_id, user_group_id=None):
2983 cls, user_id, user_group_id=None):
2984 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2984 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2985 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2985 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2986 .join(
2986 .join(
2987 Permission,
2987 Permission,
2988 UserGroupUserGroupToPerm.permission_id ==
2988 UserGroupUserGroupToPerm.permission_id ==
2989 Permission.permission_id)\
2989 Permission.permission_id)\
2990 .join(
2990 .join(
2991 TargetUserGroup,
2991 TargetUserGroup,
2992 UserGroupUserGroupToPerm.target_user_group_id ==
2992 UserGroupUserGroupToPerm.target_user_group_id ==
2993 TargetUserGroup.users_group_id)\
2993 TargetUserGroup.users_group_id)\
2994 .join(
2994 .join(
2995 UserGroup,
2995 UserGroup,
2996 UserGroupUserGroupToPerm.user_group_id ==
2996 UserGroupUserGroupToPerm.user_group_id ==
2997 UserGroup.users_group_id)\
2997 UserGroup.users_group_id)\
2998 .join(
2998 .join(
2999 UserGroupMember,
2999 UserGroupMember,
3000 UserGroupUserGroupToPerm.user_group_id ==
3000 UserGroupUserGroupToPerm.user_group_id ==
3001 UserGroupMember.users_group_id)\
3001 UserGroupMember.users_group_id)\
3002 .filter(
3002 .filter(
3003 UserGroupMember.user_id == user_id,
3003 UserGroupMember.user_id == user_id,
3004 UserGroup.users_group_active == true())
3004 UserGroup.users_group_active == true())
3005 if user_group_id:
3005 if user_group_id:
3006 q = q.filter(
3006 q = q.filter(
3007 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3007 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3008
3008
3009 return q.all()
3009 return q.all()
3010
3010
3011
3011
3012 class UserRepoToPerm(Base, BaseModel):
3012 class UserRepoToPerm(Base, BaseModel):
3013 __tablename__ = 'repo_to_perm'
3013 __tablename__ = 'repo_to_perm'
3014 __table_args__ = (
3014 __table_args__ = (
3015 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3015 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3016 base_table_args
3016 base_table_args
3017 )
3017 )
3018
3018
3019 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3019 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3020 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3020 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3021 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3021 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3022 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3022 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3023
3023
3024 user = relationship('User')
3024 user = relationship('User')
3025 repository = relationship('Repository')
3025 repository = relationship('Repository')
3026 permission = relationship('Permission')
3026 permission = relationship('Permission')
3027
3027
3028 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3028 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3029
3029
3030 @classmethod
3030 @classmethod
3031 def create(cls, user, repository, permission):
3031 def create(cls, user, repository, permission):
3032 n = cls()
3032 n = cls()
3033 n.user = user
3033 n.user = user
3034 n.repository = repository
3034 n.repository = repository
3035 n.permission = permission
3035 n.permission = permission
3036 Session().add(n)
3036 Session().add(n)
3037 return n
3037 return n
3038
3038
3039 def __unicode__(self):
3039 def __unicode__(self):
3040 return u'<%s => %s >' % (self.user, self.repository)
3040 return u'<%s => %s >' % (self.user, self.repository)
3041
3041
3042
3042
3043 class UserUserGroupToPerm(Base, BaseModel):
3043 class UserUserGroupToPerm(Base, BaseModel):
3044 __tablename__ = 'user_user_group_to_perm'
3044 __tablename__ = 'user_user_group_to_perm'
3045 __table_args__ = (
3045 __table_args__ = (
3046 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3046 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3047 base_table_args
3047 base_table_args
3048 )
3048 )
3049
3049
3050 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3050 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3051 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3051 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3052 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3052 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3053 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3053 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3054
3054
3055 user = relationship('User')
3055 user = relationship('User')
3056 user_group = relationship('UserGroup')
3056 user_group = relationship('UserGroup')
3057 permission = relationship('Permission')
3057 permission = relationship('Permission')
3058
3058
3059 @classmethod
3059 @classmethod
3060 def create(cls, user, user_group, permission):
3060 def create(cls, user, user_group, permission):
3061 n = cls()
3061 n = cls()
3062 n.user = user
3062 n.user = user
3063 n.user_group = user_group
3063 n.user_group = user_group
3064 n.permission = permission
3064 n.permission = permission
3065 Session().add(n)
3065 Session().add(n)
3066 return n
3066 return n
3067
3067
3068 def __unicode__(self):
3068 def __unicode__(self):
3069 return u'<%s => %s >' % (self.user, self.user_group)
3069 return u'<%s => %s >' % (self.user, self.user_group)
3070
3070
3071
3071
3072 class UserToPerm(Base, BaseModel):
3072 class UserToPerm(Base, BaseModel):
3073 __tablename__ = 'user_to_perm'
3073 __tablename__ = 'user_to_perm'
3074 __table_args__ = (
3074 __table_args__ = (
3075 UniqueConstraint('user_id', 'permission_id'),
3075 UniqueConstraint('user_id', 'permission_id'),
3076 base_table_args
3076 base_table_args
3077 )
3077 )
3078
3078
3079 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3079 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3080 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3080 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3081 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3081 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3082
3082
3083 user = relationship('User')
3083 user = relationship('User')
3084 permission = relationship('Permission', lazy='joined')
3084 permission = relationship('Permission', lazy='joined')
3085
3085
3086 def __unicode__(self):
3086 def __unicode__(self):
3087 return u'<%s => %s >' % (self.user, self.permission)
3087 return u'<%s => %s >' % (self.user, self.permission)
3088
3088
3089
3089
3090 class UserGroupRepoToPerm(Base, BaseModel):
3090 class UserGroupRepoToPerm(Base, BaseModel):
3091 __tablename__ = 'users_group_repo_to_perm'
3091 __tablename__ = 'users_group_repo_to_perm'
3092 __table_args__ = (
3092 __table_args__ = (
3093 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3093 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3094 base_table_args
3094 base_table_args
3095 )
3095 )
3096
3096
3097 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3097 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3098 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3098 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3099 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3099 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3100 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3100 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3101
3101
3102 users_group = relationship('UserGroup')
3102 users_group = relationship('UserGroup')
3103 permission = relationship('Permission')
3103 permission = relationship('Permission')
3104 repository = relationship('Repository')
3104 repository = relationship('Repository')
3105 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3105 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3106
3106
3107 @classmethod
3107 @classmethod
3108 def create(cls, users_group, repository, permission):
3108 def create(cls, users_group, repository, permission):
3109 n = cls()
3109 n = cls()
3110 n.users_group = users_group
3110 n.users_group = users_group
3111 n.repository = repository
3111 n.repository = repository
3112 n.permission = permission
3112 n.permission = permission
3113 Session().add(n)
3113 Session().add(n)
3114 return n
3114 return n
3115
3115
3116 def __unicode__(self):
3116 def __unicode__(self):
3117 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3117 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3118
3118
3119
3119
3120 class UserGroupUserGroupToPerm(Base, BaseModel):
3120 class UserGroupUserGroupToPerm(Base, BaseModel):
3121 __tablename__ = 'user_group_user_group_to_perm'
3121 __tablename__ = 'user_group_user_group_to_perm'
3122 __table_args__ = (
3122 __table_args__ = (
3123 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3123 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3124 CheckConstraint('target_user_group_id != user_group_id'),
3124 CheckConstraint('target_user_group_id != user_group_id'),
3125 base_table_args
3125 base_table_args
3126 )
3126 )
3127
3127
3128 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)
3128 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)
3129 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3129 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3130 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3130 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3131 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3131 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3132
3132
3133 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3133 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3134 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3134 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3135 permission = relationship('Permission')
3135 permission = relationship('Permission')
3136
3136
3137 @classmethod
3137 @classmethod
3138 def create(cls, target_user_group, user_group, permission):
3138 def create(cls, target_user_group, user_group, permission):
3139 n = cls()
3139 n = cls()
3140 n.target_user_group = target_user_group
3140 n.target_user_group = target_user_group
3141 n.user_group = user_group
3141 n.user_group = user_group
3142 n.permission = permission
3142 n.permission = permission
3143 Session().add(n)
3143 Session().add(n)
3144 return n
3144 return n
3145
3145
3146 def __unicode__(self):
3146 def __unicode__(self):
3147 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3147 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3148
3148
3149
3149
3150 class UserGroupToPerm(Base, BaseModel):
3150 class UserGroupToPerm(Base, BaseModel):
3151 __tablename__ = 'users_group_to_perm'
3151 __tablename__ = 'users_group_to_perm'
3152 __table_args__ = (
3152 __table_args__ = (
3153 UniqueConstraint('users_group_id', 'permission_id',),
3153 UniqueConstraint('users_group_id', 'permission_id',),
3154 base_table_args
3154 base_table_args
3155 )
3155 )
3156
3156
3157 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3157 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3158 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3158 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3159 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3159 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3160
3160
3161 users_group = relationship('UserGroup')
3161 users_group = relationship('UserGroup')
3162 permission = relationship('Permission')
3162 permission = relationship('Permission')
3163
3163
3164
3164
3165 class UserRepoGroupToPerm(Base, BaseModel):
3165 class UserRepoGroupToPerm(Base, BaseModel):
3166 __tablename__ = 'user_repo_group_to_perm'
3166 __tablename__ = 'user_repo_group_to_perm'
3167 __table_args__ = (
3167 __table_args__ = (
3168 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3168 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3169 base_table_args
3169 base_table_args
3170 )
3170 )
3171
3171
3172 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3172 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3174 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3174 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3175 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3175 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3176
3176
3177 user = relationship('User')
3177 user = relationship('User')
3178 group = relationship('RepoGroup')
3178 group = relationship('RepoGroup')
3179 permission = relationship('Permission')
3179 permission = relationship('Permission')
3180
3180
3181 @classmethod
3181 @classmethod
3182 def create(cls, user, repository_group, permission):
3182 def create(cls, user, repository_group, permission):
3183 n = cls()
3183 n = cls()
3184 n.user = user
3184 n.user = user
3185 n.group = repository_group
3185 n.group = repository_group
3186 n.permission = permission
3186 n.permission = permission
3187 Session().add(n)
3187 Session().add(n)
3188 return n
3188 return n
3189
3189
3190
3190
3191 class UserGroupRepoGroupToPerm(Base, BaseModel):
3191 class UserGroupRepoGroupToPerm(Base, BaseModel):
3192 __tablename__ = 'users_group_repo_group_to_perm'
3192 __tablename__ = 'users_group_repo_group_to_perm'
3193 __table_args__ = (
3193 __table_args__ = (
3194 UniqueConstraint('users_group_id', 'group_id'),
3194 UniqueConstraint('users_group_id', 'group_id'),
3195 base_table_args
3195 base_table_args
3196 )
3196 )
3197
3197
3198 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)
3198 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)
3199 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3199 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3200 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3200 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3201 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3201 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3202
3202
3203 users_group = relationship('UserGroup')
3203 users_group = relationship('UserGroup')
3204 permission = relationship('Permission')
3204 permission = relationship('Permission')
3205 group = relationship('RepoGroup')
3205 group = relationship('RepoGroup')
3206
3206
3207 @classmethod
3207 @classmethod
3208 def create(cls, user_group, repository_group, permission):
3208 def create(cls, user_group, repository_group, permission):
3209 n = cls()
3209 n = cls()
3210 n.users_group = user_group
3210 n.users_group = user_group
3211 n.group = repository_group
3211 n.group = repository_group
3212 n.permission = permission
3212 n.permission = permission
3213 Session().add(n)
3213 Session().add(n)
3214 return n
3214 return n
3215
3215
3216 def __unicode__(self):
3216 def __unicode__(self):
3217 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3217 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3218
3218
3219
3219
3220 class Statistics(Base, BaseModel):
3220 class Statistics(Base, BaseModel):
3221 __tablename__ = 'statistics'
3221 __tablename__ = 'statistics'
3222 __table_args__ = (
3222 __table_args__ = (
3223 base_table_args
3223 base_table_args
3224 )
3224 )
3225
3225
3226 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3226 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3227 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3227 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3228 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3228 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3229 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3229 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3230 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3230 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3231 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3231 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3232
3232
3233 repository = relationship('Repository', single_parent=True)
3233 repository = relationship('Repository', single_parent=True)
3234
3234
3235
3235
3236 class UserFollowing(Base, BaseModel):
3236 class UserFollowing(Base, BaseModel):
3237 __tablename__ = 'user_followings'
3237 __tablename__ = 'user_followings'
3238 __table_args__ = (
3238 __table_args__ = (
3239 UniqueConstraint('user_id', 'follows_repository_id'),
3239 UniqueConstraint('user_id', 'follows_repository_id'),
3240 UniqueConstraint('user_id', 'follows_user_id'),
3240 UniqueConstraint('user_id', 'follows_user_id'),
3241 base_table_args
3241 base_table_args
3242 )
3242 )
3243
3243
3244 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3244 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3245 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3245 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3246 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3246 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3247 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3247 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3248 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3248 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3249
3249
3250 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3250 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3251
3251
3252 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3252 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3253 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3253 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3254
3254
3255 @classmethod
3255 @classmethod
3256 def get_repo_followers(cls, repo_id):
3256 def get_repo_followers(cls, repo_id):
3257 return cls.query().filter(cls.follows_repo_id == repo_id)
3257 return cls.query().filter(cls.follows_repo_id == repo_id)
3258
3258
3259
3259
3260 class CacheKey(Base, BaseModel):
3260 class CacheKey(Base, BaseModel):
3261 __tablename__ = 'cache_invalidation'
3261 __tablename__ = 'cache_invalidation'
3262 __table_args__ = (
3262 __table_args__ = (
3263 UniqueConstraint('cache_key'),
3263 UniqueConstraint('cache_key'),
3264 Index('key_idx', 'cache_key'),
3264 Index('key_idx', 'cache_key'),
3265 base_table_args,
3265 base_table_args,
3266 )
3266 )
3267
3267
3268 CACHE_TYPE_FEED = 'FEED'
3268 CACHE_TYPE_FEED = 'FEED'
3269 CACHE_TYPE_README = 'README'
3269 CACHE_TYPE_README = 'README'
3270 # namespaces used to register process/thread aware caches
3270 # namespaces used to register process/thread aware caches
3271 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3271 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3272 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3272 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3273
3273
3274 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3274 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3275 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3275 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3276 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3276 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3277 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3277 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3278
3278
3279 def __init__(self, cache_key, cache_args=''):
3279 def __init__(self, cache_key, cache_args=''):
3280 self.cache_key = cache_key
3280 self.cache_key = cache_key
3281 self.cache_args = cache_args
3281 self.cache_args = cache_args
3282 self.cache_active = False
3282 self.cache_active = False
3283
3283
3284 def __unicode__(self):
3284 def __unicode__(self):
3285 return u"<%s('%s:%s[%s]')>" % (
3285 return u"<%s('%s:%s[%s]')>" % (
3286 self.__class__.__name__,
3286 self.__class__.__name__,
3287 self.cache_id, self.cache_key, self.cache_active)
3287 self.cache_id, self.cache_key, self.cache_active)
3288
3288
3289 def _cache_key_partition(self):
3289 def _cache_key_partition(self):
3290 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3290 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3291 return prefix, repo_name, suffix
3291 return prefix, repo_name, suffix
3292
3292
3293 def get_prefix(self):
3293 def get_prefix(self):
3294 """
3294 """
3295 Try to extract prefix from existing cache key. The key could consist
3295 Try to extract prefix from existing cache key. The key could consist
3296 of prefix, repo_name, suffix
3296 of prefix, repo_name, suffix
3297 """
3297 """
3298 # this returns prefix, repo_name, suffix
3298 # this returns prefix, repo_name, suffix
3299 return self._cache_key_partition()[0]
3299 return self._cache_key_partition()[0]
3300
3300
3301 def get_suffix(self):
3301 def get_suffix(self):
3302 """
3302 """
3303 get suffix that might have been used in _get_cache_key to
3303 get suffix that might have been used in _get_cache_key to
3304 generate self.cache_key. Only used for informational purposes
3304 generate self.cache_key. Only used for informational purposes
3305 in repo_edit.mako.
3305 in repo_edit.mako.
3306 """
3306 """
3307 # prefix, repo_name, suffix
3307 # prefix, repo_name, suffix
3308 return self._cache_key_partition()[2]
3308 return self._cache_key_partition()[2]
3309
3309
3310 @classmethod
3310 @classmethod
3311 def delete_all_cache(cls):
3311 def delete_all_cache(cls):
3312 """
3312 """
3313 Delete all cache keys from database.
3313 Delete all cache keys from database.
3314 Should only be run when all instances are down and all entries
3314 Should only be run when all instances are down and all entries
3315 thus stale.
3315 thus stale.
3316 """
3316 """
3317 cls.query().delete()
3317 cls.query().delete()
3318 Session().commit()
3318 Session().commit()
3319
3319
3320 @classmethod
3320 @classmethod
3321 def set_invalidate(cls, cache_uid, delete=False):
3321 def set_invalidate(cls, cache_uid, delete=False):
3322 """
3322 """
3323 Mark all caches of a repo as invalid in the database.
3323 Mark all caches of a repo as invalid in the database.
3324 """
3324 """
3325
3325
3326 try:
3326 try:
3327 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3327 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3328 if delete:
3328 if delete:
3329 qry.delete()
3329 qry.delete()
3330 log.debug('cache objects deleted for cache args %s',
3330 log.debug('cache objects deleted for cache args %s',
3331 safe_str(cache_uid))
3331 safe_str(cache_uid))
3332 else:
3332 else:
3333 qry.update({"cache_active": False})
3333 qry.update({"cache_active": False})
3334 log.debug('cache objects marked as invalid for cache args %s',
3334 log.debug('cache objects marked as invalid for cache args %s',
3335 safe_str(cache_uid))
3335 safe_str(cache_uid))
3336
3336
3337 Session().commit()
3337 Session().commit()
3338 except Exception:
3338 except Exception:
3339 log.exception(
3339 log.exception(
3340 'Cache key invalidation failed for cache args %s',
3340 'Cache key invalidation failed for cache args %s',
3341 safe_str(cache_uid))
3341 safe_str(cache_uid))
3342 Session().rollback()
3342 Session().rollback()
3343
3343
3344 @classmethod
3344 @classmethod
3345 def get_active_cache(cls, cache_key):
3345 def get_active_cache(cls, cache_key):
3346 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3346 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3347 if inv_obj:
3347 if inv_obj:
3348 return inv_obj
3348 return inv_obj
3349 return None
3349 return None
3350
3350
3351
3351
3352 class ChangesetComment(Base, BaseModel):
3352 class ChangesetComment(Base, BaseModel):
3353 __tablename__ = 'changeset_comments'
3353 __tablename__ = 'changeset_comments'
3354 __table_args__ = (
3354 __table_args__ = (
3355 Index('cc_revision_idx', 'revision'),
3355 Index('cc_revision_idx', 'revision'),
3356 base_table_args,
3356 base_table_args,
3357 )
3357 )
3358
3358
3359 COMMENT_OUTDATED = u'comment_outdated'
3359 COMMENT_OUTDATED = u'comment_outdated'
3360 COMMENT_TYPE_NOTE = u'note'
3360 COMMENT_TYPE_NOTE = u'note'
3361 COMMENT_TYPE_TODO = u'todo'
3361 COMMENT_TYPE_TODO = u'todo'
3362 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3362 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3363
3363
3364 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3364 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3365 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3365 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3366 revision = Column('revision', String(40), nullable=True)
3366 revision = Column('revision', String(40), nullable=True)
3367 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3367 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3368 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3368 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3369 line_no = Column('line_no', Unicode(10), nullable=True)
3369 line_no = Column('line_no', Unicode(10), nullable=True)
3370 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3370 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3371 f_path = Column('f_path', Unicode(1000), nullable=True)
3371 f_path = Column('f_path', Unicode(1000), nullable=True)
3372 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3372 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3373 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3373 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3374 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3374 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3375 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3375 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3376 renderer = Column('renderer', Unicode(64), nullable=True)
3376 renderer = Column('renderer', Unicode(64), nullable=True)
3377 display_state = Column('display_state', Unicode(128), nullable=True)
3377 display_state = Column('display_state', Unicode(128), nullable=True)
3378
3378
3379 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3379 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3380 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3380 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3381 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3381 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3382 author = relationship('User', lazy='joined')
3382 author = relationship('User', lazy='joined')
3383 repo = relationship('Repository')
3383 repo = relationship('Repository')
3384 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3384 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3385 pull_request = relationship('PullRequest', lazy='joined')
3385 pull_request = relationship('PullRequest', lazy='joined')
3386 pull_request_version = relationship('PullRequestVersion')
3386 pull_request_version = relationship('PullRequestVersion')
3387
3387
3388 @classmethod
3388 @classmethod
3389 def get_users(cls, revision=None, pull_request_id=None):
3389 def get_users(cls, revision=None, pull_request_id=None):
3390 """
3390 """
3391 Returns user associated with this ChangesetComment. ie those
3391 Returns user associated with this ChangesetComment. ie those
3392 who actually commented
3392 who actually commented
3393
3393
3394 :param cls:
3394 :param cls:
3395 :param revision:
3395 :param revision:
3396 """
3396 """
3397 q = Session().query(User)\
3397 q = Session().query(User)\
3398 .join(ChangesetComment.author)
3398 .join(ChangesetComment.author)
3399 if revision:
3399 if revision:
3400 q = q.filter(cls.revision == revision)
3400 q = q.filter(cls.revision == revision)
3401 elif pull_request_id:
3401 elif pull_request_id:
3402 q = q.filter(cls.pull_request_id == pull_request_id)
3402 q = q.filter(cls.pull_request_id == pull_request_id)
3403 return q.all()
3403 return q.all()
3404
3404
3405 @classmethod
3405 @classmethod
3406 def get_index_from_version(cls, pr_version, versions):
3406 def get_index_from_version(cls, pr_version, versions):
3407 num_versions = [x.pull_request_version_id for x in versions]
3407 num_versions = [x.pull_request_version_id for x in versions]
3408 try:
3408 try:
3409 return num_versions.index(pr_version) +1
3409 return num_versions.index(pr_version) +1
3410 except (IndexError, ValueError):
3410 except (IndexError, ValueError):
3411 return
3411 return
3412
3412
3413 @property
3413 @property
3414 def outdated(self):
3414 def outdated(self):
3415 return self.display_state == self.COMMENT_OUTDATED
3415 return self.display_state == self.COMMENT_OUTDATED
3416
3416
3417 def outdated_at_version(self, version):
3417 def outdated_at_version(self, version):
3418 """
3418 """
3419 Checks if comment is outdated for given pull request version
3419 Checks if comment is outdated for given pull request version
3420 """
3420 """
3421 return self.outdated and self.pull_request_version_id != version
3421 return self.outdated and self.pull_request_version_id != version
3422
3422
3423 def older_than_version(self, version):
3423 def older_than_version(self, version):
3424 """
3424 """
3425 Checks if comment is made from previous version than given
3425 Checks if comment is made from previous version than given
3426 """
3426 """
3427 if version is None:
3427 if version is None:
3428 return self.pull_request_version_id is not None
3428 return self.pull_request_version_id is not None
3429
3429
3430 return self.pull_request_version_id < version
3430 return self.pull_request_version_id < version
3431
3431
3432 @property
3432 @property
3433 def resolved(self):
3433 def resolved(self):
3434 return self.resolved_by[0] if self.resolved_by else None
3434 return self.resolved_by[0] if self.resolved_by else None
3435
3435
3436 @property
3436 @property
3437 def is_todo(self):
3437 def is_todo(self):
3438 return self.comment_type == self.COMMENT_TYPE_TODO
3438 return self.comment_type == self.COMMENT_TYPE_TODO
3439
3439
3440 @property
3440 @property
3441 def is_inline(self):
3441 def is_inline(self):
3442 return self.line_no and self.f_path
3442 return self.line_no and self.f_path
3443
3443
3444 def get_index_version(self, versions):
3444 def get_index_version(self, versions):
3445 return self.get_index_from_version(
3445 return self.get_index_from_version(
3446 self.pull_request_version_id, versions)
3446 self.pull_request_version_id, versions)
3447
3447
3448 def __repr__(self):
3448 def __repr__(self):
3449 if self.comment_id:
3449 if self.comment_id:
3450 return '<DB:Comment #%s>' % self.comment_id
3450 return '<DB:Comment #%s>' % self.comment_id
3451 else:
3451 else:
3452 return '<DB:Comment at %#x>' % id(self)
3452 return '<DB:Comment at %#x>' % id(self)
3453
3453
3454 def get_api_data(self):
3454 def get_api_data(self):
3455 comment = self
3455 comment = self
3456 data = {
3456 data = {
3457 'comment_id': comment.comment_id,
3457 'comment_id': comment.comment_id,
3458 'comment_type': comment.comment_type,
3458 'comment_type': comment.comment_type,
3459 'comment_text': comment.text,
3459 'comment_text': comment.text,
3460 'comment_status': comment.status_change,
3460 'comment_status': comment.status_change,
3461 'comment_f_path': comment.f_path,
3461 'comment_f_path': comment.f_path,
3462 'comment_lineno': comment.line_no,
3462 'comment_lineno': comment.line_no,
3463 'comment_author': comment.author,
3463 'comment_author': comment.author,
3464 'comment_created_on': comment.created_on
3464 'comment_created_on': comment.created_on
3465 }
3465 }
3466 return data
3466 return data
3467
3467
3468 def __json__(self):
3468 def __json__(self):
3469 data = dict()
3469 data = dict()
3470 data.update(self.get_api_data())
3470 data.update(self.get_api_data())
3471 return data
3471 return data
3472
3472
3473
3473
3474 class ChangesetStatus(Base, BaseModel):
3474 class ChangesetStatus(Base, BaseModel):
3475 __tablename__ = 'changeset_statuses'
3475 __tablename__ = 'changeset_statuses'
3476 __table_args__ = (
3476 __table_args__ = (
3477 Index('cs_revision_idx', 'revision'),
3477 Index('cs_revision_idx', 'revision'),
3478 Index('cs_version_idx', 'version'),
3478 Index('cs_version_idx', 'version'),
3479 UniqueConstraint('repo_id', 'revision', 'version'),
3479 UniqueConstraint('repo_id', 'revision', 'version'),
3480 base_table_args
3480 base_table_args
3481 )
3481 )
3482
3482
3483 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3483 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3484 STATUS_APPROVED = 'approved'
3484 STATUS_APPROVED = 'approved'
3485 STATUS_REJECTED = 'rejected'
3485 STATUS_REJECTED = 'rejected'
3486 STATUS_UNDER_REVIEW = 'under_review'
3486 STATUS_UNDER_REVIEW = 'under_review'
3487
3487
3488 STATUSES = [
3488 STATUSES = [
3489 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3489 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3490 (STATUS_APPROVED, _("Approved")),
3490 (STATUS_APPROVED, _("Approved")),
3491 (STATUS_REJECTED, _("Rejected")),
3491 (STATUS_REJECTED, _("Rejected")),
3492 (STATUS_UNDER_REVIEW, _("Under Review")),
3492 (STATUS_UNDER_REVIEW, _("Under Review")),
3493 ]
3493 ]
3494
3494
3495 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3495 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3496 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3496 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3497 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3497 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3498 revision = Column('revision', String(40), nullable=False)
3498 revision = Column('revision', String(40), nullable=False)
3499 status = Column('status', String(128), nullable=False, default=DEFAULT)
3499 status = Column('status', String(128), nullable=False, default=DEFAULT)
3500 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3500 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3501 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3501 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3502 version = Column('version', Integer(), nullable=False, default=0)
3502 version = Column('version', Integer(), nullable=False, default=0)
3503 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3503 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3504
3504
3505 author = relationship('User', lazy='joined')
3505 author = relationship('User', lazy='joined')
3506 repo = relationship('Repository')
3506 repo = relationship('Repository')
3507 comment = relationship('ChangesetComment', lazy='joined')
3507 comment = relationship('ChangesetComment', lazy='joined')
3508 pull_request = relationship('PullRequest', lazy='joined')
3508 pull_request = relationship('PullRequest', lazy='joined')
3509
3509
3510 def __unicode__(self):
3510 def __unicode__(self):
3511 return u"<%s('%s[v%s]:%s')>" % (
3511 return u"<%s('%s[v%s]:%s')>" % (
3512 self.__class__.__name__,
3512 self.__class__.__name__,
3513 self.status, self.version, self.author
3513 self.status, self.version, self.author
3514 )
3514 )
3515
3515
3516 @classmethod
3516 @classmethod
3517 def get_status_lbl(cls, value):
3517 def get_status_lbl(cls, value):
3518 return dict(cls.STATUSES).get(value)
3518 return dict(cls.STATUSES).get(value)
3519
3519
3520 @property
3520 @property
3521 def status_lbl(self):
3521 def status_lbl(self):
3522 return ChangesetStatus.get_status_lbl(self.status)
3522 return ChangesetStatus.get_status_lbl(self.status)
3523
3523
3524 def get_api_data(self):
3524 def get_api_data(self):
3525 status = self
3525 status = self
3526 data = {
3526 data = {
3527 'status_id': status.changeset_status_id,
3527 'status_id': status.changeset_status_id,
3528 'status': status.status,
3528 'status': status.status,
3529 }
3529 }
3530 return data
3530 return data
3531
3531
3532 def __json__(self):
3532 def __json__(self):
3533 data = dict()
3533 data = dict()
3534 data.update(self.get_api_data())
3534 data.update(self.get_api_data())
3535 return data
3535 return data
3536
3536
3537
3537
3538 class _PullRequestBase(BaseModel):
3538 class _PullRequestBase(BaseModel):
3539 """
3539 """
3540 Common attributes of pull request and version entries.
3540 Common attributes of pull request and version entries.
3541 """
3541 """
3542
3542
3543 # .status values
3543 # .status values
3544 STATUS_NEW = u'new'
3544 STATUS_NEW = u'new'
3545 STATUS_OPEN = u'open'
3545 STATUS_OPEN = u'open'
3546 STATUS_CLOSED = u'closed'
3546 STATUS_CLOSED = u'closed'
3547
3547
3548 title = Column('title', Unicode(255), nullable=True)
3548 title = Column('title', Unicode(255), nullable=True)
3549 description = Column(
3549 description = Column(
3550 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3550 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3551 nullable=True)
3551 nullable=True)
3552 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3552 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3553
3553
3554 # new/open/closed status of pull request (not approve/reject/etc)
3554 # new/open/closed status of pull request (not approve/reject/etc)
3555 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3555 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3556 created_on = Column(
3556 created_on = Column(
3557 'created_on', DateTime(timezone=False), nullable=False,
3557 'created_on', DateTime(timezone=False), nullable=False,
3558 default=datetime.datetime.now)
3558 default=datetime.datetime.now)
3559 updated_on = Column(
3559 updated_on = Column(
3560 'updated_on', DateTime(timezone=False), nullable=False,
3560 'updated_on', DateTime(timezone=False), nullable=False,
3561 default=datetime.datetime.now)
3561 default=datetime.datetime.now)
3562
3562
3563 @declared_attr
3563 @declared_attr
3564 def user_id(cls):
3564 def user_id(cls):
3565 return Column(
3565 return Column(
3566 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3566 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3567 unique=None)
3567 unique=None)
3568
3568
3569 # 500 revisions max
3569 # 500 revisions max
3570 _revisions = Column(
3570 _revisions = Column(
3571 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3571 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3572
3572
3573 @declared_attr
3573 @declared_attr
3574 def source_repo_id(cls):
3574 def source_repo_id(cls):
3575 # TODO: dan: rename column to source_repo_id
3575 # TODO: dan: rename column to source_repo_id
3576 return Column(
3576 return Column(
3577 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3577 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3578 nullable=False)
3578 nullable=False)
3579
3579
3580 source_ref = Column('org_ref', Unicode(255), nullable=False)
3580 source_ref = Column('org_ref', Unicode(255), nullable=False)
3581
3581
3582 @declared_attr
3582 @declared_attr
3583 def target_repo_id(cls):
3583 def target_repo_id(cls):
3584 # TODO: dan: rename column to target_repo_id
3584 # TODO: dan: rename column to target_repo_id
3585 return Column(
3585 return Column(
3586 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3586 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3587 nullable=False)
3587 nullable=False)
3588
3588
3589 target_ref = Column('other_ref', Unicode(255), nullable=False)
3589 target_ref = Column('other_ref', Unicode(255), nullable=False)
3590 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3590 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3591
3591
3592 # TODO: dan: rename column to last_merge_source_rev
3592 # TODO: dan: rename column to last_merge_source_rev
3593 _last_merge_source_rev = Column(
3593 _last_merge_source_rev = Column(
3594 'last_merge_org_rev', String(40), nullable=True)
3594 'last_merge_org_rev', String(40), nullable=True)
3595 # TODO: dan: rename column to last_merge_target_rev
3595 # TODO: dan: rename column to last_merge_target_rev
3596 _last_merge_target_rev = Column(
3596 _last_merge_target_rev = Column(
3597 'last_merge_other_rev', String(40), nullable=True)
3597 'last_merge_other_rev', String(40), nullable=True)
3598 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3598 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3599 merge_rev = Column('merge_rev', String(40), nullable=True)
3599 merge_rev = Column('merge_rev', String(40), nullable=True)
3600
3600
3601 reviewer_data = Column(
3601 reviewer_data = Column(
3602 'reviewer_data_json', MutationObj.as_mutable(
3602 'reviewer_data_json', MutationObj.as_mutable(
3603 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3603 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3604
3604
3605 @property
3605 @property
3606 def reviewer_data_json(self):
3606 def reviewer_data_json(self):
3607 return json.dumps(self.reviewer_data)
3607 return json.dumps(self.reviewer_data)
3608
3608
3609 @hybrid_property
3609 @hybrid_property
3610 def description_safe(self):
3610 def description_safe(self):
3611 from rhodecode.lib import helpers as h
3611 from rhodecode.lib import helpers as h
3612 return h.escape(self.description)
3612 return h.escape(self.description)
3613
3613
3614 @hybrid_property
3614 @hybrid_property
3615 def revisions(self):
3615 def revisions(self):
3616 return self._revisions.split(':') if self._revisions else []
3616 return self._revisions.split(':') if self._revisions else []
3617
3617
3618 @revisions.setter
3618 @revisions.setter
3619 def revisions(self, val):
3619 def revisions(self, val):
3620 self._revisions = ':'.join(val)
3620 self._revisions = ':'.join(val)
3621
3621
3622 @hybrid_property
3622 @hybrid_property
3623 def last_merge_status(self):
3623 def last_merge_status(self):
3624 return safe_int(self._last_merge_status)
3624 return safe_int(self._last_merge_status)
3625
3625
3626 @last_merge_status.setter
3626 @last_merge_status.setter
3627 def last_merge_status(self, val):
3627 def last_merge_status(self, val):
3628 self._last_merge_status = val
3628 self._last_merge_status = val
3629
3629
3630 @declared_attr
3630 @declared_attr
3631 def author(cls):
3631 def author(cls):
3632 return relationship('User', lazy='joined')
3632 return relationship('User', lazy='joined')
3633
3633
3634 @declared_attr
3634 @declared_attr
3635 def source_repo(cls):
3635 def source_repo(cls):
3636 return relationship(
3636 return relationship(
3637 'Repository',
3637 'Repository',
3638 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3638 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3639
3639
3640 @property
3640 @property
3641 def source_ref_parts(self):
3641 def source_ref_parts(self):
3642 return self.unicode_to_reference(self.source_ref)
3642 return self.unicode_to_reference(self.source_ref)
3643
3643
3644 @declared_attr
3644 @declared_attr
3645 def target_repo(cls):
3645 def target_repo(cls):
3646 return relationship(
3646 return relationship(
3647 'Repository',
3647 'Repository',
3648 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3648 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3649
3649
3650 @property
3650 @property
3651 def target_ref_parts(self):
3651 def target_ref_parts(self):
3652 return self.unicode_to_reference(self.target_ref)
3652 return self.unicode_to_reference(self.target_ref)
3653
3653
3654 @property
3654 @property
3655 def shadow_merge_ref(self):
3655 def shadow_merge_ref(self):
3656 return self.unicode_to_reference(self._shadow_merge_ref)
3656 return self.unicode_to_reference(self._shadow_merge_ref)
3657
3657
3658 @shadow_merge_ref.setter
3658 @shadow_merge_ref.setter
3659 def shadow_merge_ref(self, ref):
3659 def shadow_merge_ref(self, ref):
3660 self._shadow_merge_ref = self.reference_to_unicode(ref)
3660 self._shadow_merge_ref = self.reference_to_unicode(ref)
3661
3661
3662 def unicode_to_reference(self, raw):
3662 def unicode_to_reference(self, raw):
3663 """
3663 """
3664 Convert a unicode (or string) to a reference object.
3664 Convert a unicode (or string) to a reference object.
3665 If unicode evaluates to False it returns None.
3665 If unicode evaluates to False it returns None.
3666 """
3666 """
3667 if raw:
3667 if raw:
3668 refs = raw.split(':')
3668 refs = raw.split(':')
3669 return Reference(*refs)
3669 return Reference(*refs)
3670 else:
3670 else:
3671 return None
3671 return None
3672
3672
3673 def reference_to_unicode(self, ref):
3673 def reference_to_unicode(self, ref):
3674 """
3674 """
3675 Convert a reference object to unicode.
3675 Convert a reference object to unicode.
3676 If reference is None it returns None.
3676 If reference is None it returns None.
3677 """
3677 """
3678 if ref:
3678 if ref:
3679 return u':'.join(ref)
3679 return u':'.join(ref)
3680 else:
3680 else:
3681 return None
3681 return None
3682
3682
3683 def get_api_data(self, with_merge_state=True):
3683 def get_api_data(self, with_merge_state=True):
3684 from rhodecode.model.pull_request import PullRequestModel
3684 from rhodecode.model.pull_request import PullRequestModel
3685
3685
3686 pull_request = self
3686 pull_request = self
3687 if with_merge_state:
3687 if with_merge_state:
3688 merge_status = PullRequestModel().merge_status(pull_request)
3688 merge_status = PullRequestModel().merge_status(pull_request)
3689 merge_state = {
3689 merge_state = {
3690 'status': merge_status[0],
3690 'status': merge_status[0],
3691 'message': safe_unicode(merge_status[1]),
3691 'message': safe_unicode(merge_status[1]),
3692 }
3692 }
3693 else:
3693 else:
3694 merge_state = {'status': 'not_available',
3694 merge_state = {'status': 'not_available',
3695 'message': 'not_available'}
3695 'message': 'not_available'}
3696
3696
3697 merge_data = {
3697 merge_data = {
3698 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3698 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3699 'reference': (
3699 'reference': (
3700 pull_request.shadow_merge_ref._asdict()
3700 pull_request.shadow_merge_ref._asdict()
3701 if pull_request.shadow_merge_ref else None),
3701 if pull_request.shadow_merge_ref else None),
3702 }
3702 }
3703
3703
3704 data = {
3704 data = {
3705 'pull_request_id': pull_request.pull_request_id,
3705 'pull_request_id': pull_request.pull_request_id,
3706 'url': PullRequestModel().get_url(pull_request),
3706 'url': PullRequestModel().get_url(pull_request),
3707 'title': pull_request.title,
3707 'title': pull_request.title,
3708 'description': pull_request.description,
3708 'description': pull_request.description,
3709 'status': pull_request.status,
3709 'status': pull_request.status,
3710 'created_on': pull_request.created_on,
3710 'created_on': pull_request.created_on,
3711 'updated_on': pull_request.updated_on,
3711 'updated_on': pull_request.updated_on,
3712 'commit_ids': pull_request.revisions,
3712 'commit_ids': pull_request.revisions,
3713 'review_status': pull_request.calculated_review_status(),
3713 'review_status': pull_request.calculated_review_status(),
3714 'mergeable': merge_state,
3714 'mergeable': merge_state,
3715 'source': {
3715 'source': {
3716 'clone_url': pull_request.source_repo.clone_url(),
3716 'clone_url': pull_request.source_repo.clone_url(),
3717 'repository': pull_request.source_repo.repo_name,
3717 'repository': pull_request.source_repo.repo_name,
3718 'reference': {
3718 'reference': {
3719 'name': pull_request.source_ref_parts.name,
3719 'name': pull_request.source_ref_parts.name,
3720 'type': pull_request.source_ref_parts.type,
3720 'type': pull_request.source_ref_parts.type,
3721 'commit_id': pull_request.source_ref_parts.commit_id,
3721 'commit_id': pull_request.source_ref_parts.commit_id,
3722 },
3722 },
3723 },
3723 },
3724 'target': {
3724 'target': {
3725 'clone_url': pull_request.target_repo.clone_url(),
3725 'clone_url': pull_request.target_repo.clone_url(),
3726 'repository': pull_request.target_repo.repo_name,
3726 'repository': pull_request.target_repo.repo_name,
3727 'reference': {
3727 'reference': {
3728 'name': pull_request.target_ref_parts.name,
3728 'name': pull_request.target_ref_parts.name,
3729 'type': pull_request.target_ref_parts.type,
3729 'type': pull_request.target_ref_parts.type,
3730 'commit_id': pull_request.target_ref_parts.commit_id,
3730 'commit_id': pull_request.target_ref_parts.commit_id,
3731 },
3731 },
3732 },
3732 },
3733 'merge': merge_data,
3733 'merge': merge_data,
3734 'author': pull_request.author.get_api_data(include_secrets=False,
3734 'author': pull_request.author.get_api_data(include_secrets=False,
3735 details='basic'),
3735 details='basic'),
3736 'reviewers': [
3736 'reviewers': [
3737 {
3737 {
3738 'user': reviewer.get_api_data(include_secrets=False,
3738 'user': reviewer.get_api_data(include_secrets=False,
3739 details='basic'),
3739 details='basic'),
3740 'reasons': reasons,
3740 'reasons': reasons,
3741 'review_status': st[0][1].status if st else 'not_reviewed',
3741 'review_status': st[0][1].status if st else 'not_reviewed',
3742 }
3742 }
3743 for obj, reviewer, reasons, mandatory, st in
3743 for obj, reviewer, reasons, mandatory, st in
3744 pull_request.reviewers_statuses()
3744 pull_request.reviewers_statuses()
3745 ]
3745 ]
3746 }
3746 }
3747
3747
3748 return data
3748 return data
3749
3749
3750
3750
3751 class PullRequest(Base, _PullRequestBase):
3751 class PullRequest(Base, _PullRequestBase):
3752 __tablename__ = 'pull_requests'
3752 __tablename__ = 'pull_requests'
3753 __table_args__ = (
3753 __table_args__ = (
3754 base_table_args,
3754 base_table_args,
3755 )
3755 )
3756
3756
3757 pull_request_id = Column(
3757 pull_request_id = Column(
3758 'pull_request_id', Integer(), nullable=False, primary_key=True)
3758 'pull_request_id', Integer(), nullable=False, primary_key=True)
3759
3759
3760 def __repr__(self):
3760 def __repr__(self):
3761 if self.pull_request_id:
3761 if self.pull_request_id:
3762 return '<DB:PullRequest #%s>' % self.pull_request_id
3762 return '<DB:PullRequest #%s>' % self.pull_request_id
3763 else:
3763 else:
3764 return '<DB:PullRequest at %#x>' % id(self)
3764 return '<DB:PullRequest at %#x>' % id(self)
3765
3765
3766 reviewers = relationship('PullRequestReviewers',
3766 reviewers = relationship('PullRequestReviewers',
3767 cascade="all, delete, delete-orphan")
3767 cascade="all, delete, delete-orphan")
3768 statuses = relationship('ChangesetStatus',
3768 statuses = relationship('ChangesetStatus',
3769 cascade="all, delete, delete-orphan")
3769 cascade="all, delete, delete-orphan")
3770 comments = relationship('ChangesetComment',
3770 comments = relationship('ChangesetComment',
3771 cascade="all, delete, delete-orphan")
3771 cascade="all, delete, delete-orphan")
3772 versions = relationship('PullRequestVersion',
3772 versions = relationship('PullRequestVersion',
3773 cascade="all, delete, delete-orphan",
3773 cascade="all, delete, delete-orphan",
3774 lazy='dynamic')
3774 lazy='dynamic')
3775
3775
3776 @classmethod
3776 @classmethod
3777 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3777 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3778 internal_methods=None):
3778 internal_methods=None):
3779
3779
3780 class PullRequestDisplay(object):
3780 class PullRequestDisplay(object):
3781 """
3781 """
3782 Special object wrapper for showing PullRequest data via Versions
3782 Special object wrapper for showing PullRequest data via Versions
3783 It mimics PR object as close as possible. This is read only object
3783 It mimics PR object as close as possible. This is read only object
3784 just for display
3784 just for display
3785 """
3785 """
3786
3786
3787 def __init__(self, attrs, internal=None):
3787 def __init__(self, attrs, internal=None):
3788 self.attrs = attrs
3788 self.attrs = attrs
3789 # internal have priority over the given ones via attrs
3789 # internal have priority over the given ones via attrs
3790 self.internal = internal or ['versions']
3790 self.internal = internal or ['versions']
3791
3791
3792 def __getattr__(self, item):
3792 def __getattr__(self, item):
3793 if item in self.internal:
3793 if item in self.internal:
3794 return getattr(self, item)
3794 return getattr(self, item)
3795 try:
3795 try:
3796 return self.attrs[item]
3796 return self.attrs[item]
3797 except KeyError:
3797 except KeyError:
3798 raise AttributeError(
3798 raise AttributeError(
3799 '%s object has no attribute %s' % (self, item))
3799 '%s object has no attribute %s' % (self, item))
3800
3800
3801 def __repr__(self):
3801 def __repr__(self):
3802 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3802 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3803
3803
3804 def versions(self):
3804 def versions(self):
3805 return pull_request_obj.versions.order_by(
3805 return pull_request_obj.versions.order_by(
3806 PullRequestVersion.pull_request_version_id).all()
3806 PullRequestVersion.pull_request_version_id).all()
3807
3807
3808 def is_closed(self):
3808 def is_closed(self):
3809 return pull_request_obj.is_closed()
3809 return pull_request_obj.is_closed()
3810
3810
3811 @property
3811 @property
3812 def pull_request_version_id(self):
3812 def pull_request_version_id(self):
3813 return getattr(pull_request_obj, 'pull_request_version_id', None)
3813 return getattr(pull_request_obj, 'pull_request_version_id', None)
3814
3814
3815 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3815 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3816
3816
3817 attrs.author = StrictAttributeDict(
3817 attrs.author = StrictAttributeDict(
3818 pull_request_obj.author.get_api_data())
3818 pull_request_obj.author.get_api_data())
3819 if pull_request_obj.target_repo:
3819 if pull_request_obj.target_repo:
3820 attrs.target_repo = StrictAttributeDict(
3820 attrs.target_repo = StrictAttributeDict(
3821 pull_request_obj.target_repo.get_api_data())
3821 pull_request_obj.target_repo.get_api_data())
3822 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3822 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3823
3823
3824 if pull_request_obj.source_repo:
3824 if pull_request_obj.source_repo:
3825 attrs.source_repo = StrictAttributeDict(
3825 attrs.source_repo = StrictAttributeDict(
3826 pull_request_obj.source_repo.get_api_data())
3826 pull_request_obj.source_repo.get_api_data())
3827 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3827 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3828
3828
3829 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3829 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3830 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3830 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3831 attrs.revisions = pull_request_obj.revisions
3831 attrs.revisions = pull_request_obj.revisions
3832
3832
3833 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3833 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3834 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3834 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3835 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3835 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3836
3836
3837 return PullRequestDisplay(attrs, internal=internal_methods)
3837 return PullRequestDisplay(attrs, internal=internal_methods)
3838
3838
3839 def is_closed(self):
3839 def is_closed(self):
3840 return self.status == self.STATUS_CLOSED
3840 return self.status == self.STATUS_CLOSED
3841
3841
3842 def __json__(self):
3842 def __json__(self):
3843 return {
3843 return {
3844 'revisions': self.revisions,
3844 'revisions': self.revisions,
3845 }
3845 }
3846
3846
3847 def calculated_review_status(self):
3847 def calculated_review_status(self):
3848 from rhodecode.model.changeset_status import ChangesetStatusModel
3848 from rhodecode.model.changeset_status import ChangesetStatusModel
3849 return ChangesetStatusModel().calculated_review_status(self)
3849 return ChangesetStatusModel().calculated_review_status(self)
3850
3850
3851 def reviewers_statuses(self):
3851 def reviewers_statuses(self):
3852 from rhodecode.model.changeset_status import ChangesetStatusModel
3852 from rhodecode.model.changeset_status import ChangesetStatusModel
3853 return ChangesetStatusModel().reviewers_statuses(self)
3853 return ChangesetStatusModel().reviewers_statuses(self)
3854
3854
3855 @property
3855 @property
3856 def workspace_id(self):
3856 def workspace_id(self):
3857 from rhodecode.model.pull_request import PullRequestModel
3857 from rhodecode.model.pull_request import PullRequestModel
3858 return PullRequestModel()._workspace_id(self)
3858 return PullRequestModel()._workspace_id(self)
3859
3859
3860 def get_shadow_repo(self):
3860 def get_shadow_repo(self):
3861 workspace_id = self.workspace_id
3861 workspace_id = self.workspace_id
3862 vcs_obj = self.target_repo.scm_instance()
3862 vcs_obj = self.target_repo.scm_instance()
3863 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3863 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3864 self.target_repo.repo_id, workspace_id)
3864 self.target_repo.repo_id, workspace_id)
3865 if os.path.isdir(shadow_repository_path):
3865 if os.path.isdir(shadow_repository_path):
3866 return vcs_obj._get_shadow_instance(shadow_repository_path)
3866 return vcs_obj._get_shadow_instance(shadow_repository_path)
3867
3867
3868
3868
3869 class PullRequestVersion(Base, _PullRequestBase):
3869 class PullRequestVersion(Base, _PullRequestBase):
3870 __tablename__ = 'pull_request_versions'
3870 __tablename__ = 'pull_request_versions'
3871 __table_args__ = (
3871 __table_args__ = (
3872 base_table_args,
3872 base_table_args,
3873 )
3873 )
3874
3874
3875 pull_request_version_id = Column(
3875 pull_request_version_id = Column(
3876 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3876 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3877 pull_request_id = Column(
3877 pull_request_id = Column(
3878 'pull_request_id', Integer(),
3878 'pull_request_id', Integer(),
3879 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3879 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3880 pull_request = relationship('PullRequest')
3880 pull_request = relationship('PullRequest')
3881
3881
3882 def __repr__(self):
3882 def __repr__(self):
3883 if self.pull_request_version_id:
3883 if self.pull_request_version_id:
3884 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3884 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3885 else:
3885 else:
3886 return '<DB:PullRequestVersion at %#x>' % id(self)
3886 return '<DB:PullRequestVersion at %#x>' % id(self)
3887
3887
3888 @property
3888 @property
3889 def reviewers(self):
3889 def reviewers(self):
3890 return self.pull_request.reviewers
3890 return self.pull_request.reviewers
3891
3891
3892 @property
3892 @property
3893 def versions(self):
3893 def versions(self):
3894 return self.pull_request.versions
3894 return self.pull_request.versions
3895
3895
3896 def is_closed(self):
3896 def is_closed(self):
3897 # calculate from original
3897 # calculate from original
3898 return self.pull_request.status == self.STATUS_CLOSED
3898 return self.pull_request.status == self.STATUS_CLOSED
3899
3899
3900 def calculated_review_status(self):
3900 def calculated_review_status(self):
3901 return self.pull_request.calculated_review_status()
3901 return self.pull_request.calculated_review_status()
3902
3902
3903 def reviewers_statuses(self):
3903 def reviewers_statuses(self):
3904 return self.pull_request.reviewers_statuses()
3904 return self.pull_request.reviewers_statuses()
3905
3905
3906
3906
3907 class PullRequestReviewers(Base, BaseModel):
3907 class PullRequestReviewers(Base, BaseModel):
3908 __tablename__ = 'pull_request_reviewers'
3908 __tablename__ = 'pull_request_reviewers'
3909 __table_args__ = (
3909 __table_args__ = (
3910 base_table_args,
3910 base_table_args,
3911 )
3911 )
3912
3912
3913 @hybrid_property
3913 @hybrid_property
3914 def reasons(self):
3914 def reasons(self):
3915 if not self._reasons:
3915 if not self._reasons:
3916 return []
3916 return []
3917 return self._reasons
3917 return self._reasons
3918
3918
3919 @reasons.setter
3919 @reasons.setter
3920 def reasons(self, val):
3920 def reasons(self, val):
3921 val = val or []
3921 val = val or []
3922 if any(not isinstance(x, basestring) for x in val):
3922 if any(not isinstance(x, basestring) for x in val):
3923 raise Exception('invalid reasons type, must be list of strings')
3923 raise Exception('invalid reasons type, must be list of strings')
3924 self._reasons = val
3924 self._reasons = val
3925
3925
3926 pull_requests_reviewers_id = Column(
3926 pull_requests_reviewers_id = Column(
3927 'pull_requests_reviewers_id', Integer(), nullable=False,
3927 'pull_requests_reviewers_id', Integer(), nullable=False,
3928 primary_key=True)
3928 primary_key=True)
3929 pull_request_id = Column(
3929 pull_request_id = Column(
3930 "pull_request_id", Integer(),
3930 "pull_request_id", Integer(),
3931 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3931 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3932 user_id = Column(
3932 user_id = Column(
3933 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3933 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3934 _reasons = Column(
3934 _reasons = Column(
3935 'reason', MutationList.as_mutable(
3935 'reason', MutationList.as_mutable(
3936 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3936 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3937
3937
3938 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3938 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3939 user = relationship('User')
3939 user = relationship('User')
3940 pull_request = relationship('PullRequest')
3940 pull_request = relationship('PullRequest')
3941
3941
3942 rule_data = Column(
3942 rule_data = Column(
3943 'rule_data_json',
3943 'rule_data_json',
3944 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3944 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3945
3945
3946 def rule_user_group_data(self):
3946 def rule_user_group_data(self):
3947 """
3947 """
3948 Returns the voting user group rule data for this reviewer
3948 Returns the voting user group rule data for this reviewer
3949 """
3949 """
3950
3950
3951 if self.rule_data and 'vote_rule' in self.rule_data:
3951 if self.rule_data and 'vote_rule' in self.rule_data:
3952 user_group_data = {}
3952 user_group_data = {}
3953 if 'rule_user_group_entry_id' in self.rule_data:
3953 if 'rule_user_group_entry_id' in self.rule_data:
3954 # means a group with voting rules !
3954 # means a group with voting rules !
3955 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3955 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3956 user_group_data['name'] = self.rule_data['rule_name']
3956 user_group_data['name'] = self.rule_data['rule_name']
3957 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3957 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3958
3958
3959 return user_group_data
3959 return user_group_data
3960
3960
3961 def __unicode__(self):
3961 def __unicode__(self):
3962 return u"<%s('id:%s')>" % (self.__class__.__name__,
3962 return u"<%s('id:%s')>" % (self.__class__.__name__,
3963 self.pull_requests_reviewers_id)
3963 self.pull_requests_reviewers_id)
3964
3964
3965
3965
3966 class Notification(Base, BaseModel):
3966 class Notification(Base, BaseModel):
3967 __tablename__ = 'notifications'
3967 __tablename__ = 'notifications'
3968 __table_args__ = (
3968 __table_args__ = (
3969 Index('notification_type_idx', 'type'),
3969 Index('notification_type_idx', 'type'),
3970 base_table_args,
3970 base_table_args,
3971 )
3971 )
3972
3972
3973 TYPE_CHANGESET_COMMENT = u'cs_comment'
3973 TYPE_CHANGESET_COMMENT = u'cs_comment'
3974 TYPE_MESSAGE = u'message'
3974 TYPE_MESSAGE = u'message'
3975 TYPE_MENTION = u'mention'
3975 TYPE_MENTION = u'mention'
3976 TYPE_REGISTRATION = u'registration'
3976 TYPE_REGISTRATION = u'registration'
3977 TYPE_PULL_REQUEST = u'pull_request'
3977 TYPE_PULL_REQUEST = u'pull_request'
3978 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3978 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3979
3979
3980 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3980 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3981 subject = Column('subject', Unicode(512), nullable=True)
3981 subject = Column('subject', Unicode(512), nullable=True)
3982 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3982 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3983 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3983 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3984 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3984 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3985 type_ = Column('type', Unicode(255))
3985 type_ = Column('type', Unicode(255))
3986
3986
3987 created_by_user = relationship('User')
3987 created_by_user = relationship('User')
3988 notifications_to_users = relationship('UserNotification', lazy='joined',
3988 notifications_to_users = relationship('UserNotification', lazy='joined',
3989 cascade="all, delete, delete-orphan")
3989 cascade="all, delete, delete-orphan")
3990
3990
3991 @property
3991 @property
3992 def recipients(self):
3992 def recipients(self):
3993 return [x.user for x in UserNotification.query()\
3993 return [x.user for x in UserNotification.query()\
3994 .filter(UserNotification.notification == self)\
3994 .filter(UserNotification.notification == self)\
3995 .order_by(UserNotification.user_id.asc()).all()]
3995 .order_by(UserNotification.user_id.asc()).all()]
3996
3996
3997 @classmethod
3997 @classmethod
3998 def create(cls, created_by, subject, body, recipients, type_=None):
3998 def create(cls, created_by, subject, body, recipients, type_=None):
3999 if type_ is None:
3999 if type_ is None:
4000 type_ = Notification.TYPE_MESSAGE
4000 type_ = Notification.TYPE_MESSAGE
4001
4001
4002 notification = cls()
4002 notification = cls()
4003 notification.created_by_user = created_by
4003 notification.created_by_user = created_by
4004 notification.subject = subject
4004 notification.subject = subject
4005 notification.body = body
4005 notification.body = body
4006 notification.type_ = type_
4006 notification.type_ = type_
4007 notification.created_on = datetime.datetime.now()
4007 notification.created_on = datetime.datetime.now()
4008
4008
4009 # For each recipient link the created notification to his account
4009 # For each recipient link the created notification to his account
4010 for u in recipients:
4010 for u in recipients:
4011 assoc = UserNotification()
4011 assoc = UserNotification()
4012 assoc.user_id = u.user_id
4012 assoc.user_id = u.user_id
4013 assoc.notification = notification
4013 assoc.notification = notification
4014
4014
4015 # if created_by is inside recipients mark his notification
4015 # if created_by is inside recipients mark his notification
4016 # as read
4016 # as read
4017 if u.user_id == created_by.user_id:
4017 if u.user_id == created_by.user_id:
4018 assoc.read = True
4018 assoc.read = True
4019 Session().add(assoc)
4019 Session().add(assoc)
4020
4020
4021 Session().add(notification)
4021 Session().add(notification)
4022
4022
4023 return notification
4023 return notification
4024
4024
4025
4025
4026 class UserNotification(Base, BaseModel):
4026 class UserNotification(Base, BaseModel):
4027 __tablename__ = 'user_to_notification'
4027 __tablename__ = 'user_to_notification'
4028 __table_args__ = (
4028 __table_args__ = (
4029 UniqueConstraint('user_id', 'notification_id'),
4029 UniqueConstraint('user_id', 'notification_id'),
4030 base_table_args
4030 base_table_args
4031 )
4031 )
4032
4032
4033 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4033 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4034 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4034 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4035 read = Column('read', Boolean, default=False)
4035 read = Column('read', Boolean, default=False)
4036 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4036 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4037
4037
4038 user = relationship('User', lazy="joined")
4038 user = relationship('User', lazy="joined")
4039 notification = relationship('Notification', lazy="joined",
4039 notification = relationship('Notification', lazy="joined",
4040 order_by=lambda: Notification.created_on.desc(),)
4040 order_by=lambda: Notification.created_on.desc(),)
4041
4041
4042 def mark_as_read(self):
4042 def mark_as_read(self):
4043 self.read = True
4043 self.read = True
4044 Session().add(self)
4044 Session().add(self)
4045
4045
4046
4046
4047 class Gist(Base, BaseModel):
4047 class Gist(Base, BaseModel):
4048 __tablename__ = 'gists'
4048 __tablename__ = 'gists'
4049 __table_args__ = (
4049 __table_args__ = (
4050 Index('g_gist_access_id_idx', 'gist_access_id'),
4050 Index('g_gist_access_id_idx', 'gist_access_id'),
4051 Index('g_created_on_idx', 'created_on'),
4051 Index('g_created_on_idx', 'created_on'),
4052 base_table_args
4052 base_table_args
4053 )
4053 )
4054
4054
4055 GIST_PUBLIC = u'public'
4055 GIST_PUBLIC = u'public'
4056 GIST_PRIVATE = u'private'
4056 GIST_PRIVATE = u'private'
4057 DEFAULT_FILENAME = u'gistfile1.txt'
4057 DEFAULT_FILENAME = u'gistfile1.txt'
4058
4058
4059 ACL_LEVEL_PUBLIC = u'acl_public'
4059 ACL_LEVEL_PUBLIC = u'acl_public'
4060 ACL_LEVEL_PRIVATE = u'acl_private'
4060 ACL_LEVEL_PRIVATE = u'acl_private'
4061
4061
4062 gist_id = Column('gist_id', Integer(), primary_key=True)
4062 gist_id = Column('gist_id', Integer(), primary_key=True)
4063 gist_access_id = Column('gist_access_id', Unicode(250))
4063 gist_access_id = Column('gist_access_id', Unicode(250))
4064 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4064 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4065 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4065 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4066 gist_expires = Column('gist_expires', Float(53), nullable=False)
4066 gist_expires = Column('gist_expires', Float(53), nullable=False)
4067 gist_type = Column('gist_type', Unicode(128), nullable=False)
4067 gist_type = Column('gist_type', Unicode(128), nullable=False)
4068 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4068 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4069 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4069 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4070 acl_level = Column('acl_level', Unicode(128), nullable=True)
4070 acl_level = Column('acl_level', Unicode(128), nullable=True)
4071
4071
4072 owner = relationship('User')
4072 owner = relationship('User')
4073
4073
4074 def __repr__(self):
4074 def __repr__(self):
4075 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4075 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4076
4076
4077 @hybrid_property
4077 @hybrid_property
4078 def description_safe(self):
4078 def description_safe(self):
4079 from rhodecode.lib import helpers as h
4079 from rhodecode.lib import helpers as h
4080 return h.escape(self.gist_description)
4080 return h.escape(self.gist_description)
4081
4081
4082 @classmethod
4082 @classmethod
4083 def get_or_404(cls, id_):
4083 def get_or_404(cls, id_):
4084 from pyramid.httpexceptions import HTTPNotFound
4084 from pyramid.httpexceptions import HTTPNotFound
4085
4085
4086 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4086 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4087 if not res:
4087 if not res:
4088 raise HTTPNotFound()
4088 raise HTTPNotFound()
4089 return res
4089 return res
4090
4090
4091 @classmethod
4091 @classmethod
4092 def get_by_access_id(cls, gist_access_id):
4092 def get_by_access_id(cls, gist_access_id):
4093 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4093 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4094
4094
4095 def gist_url(self):
4095 def gist_url(self):
4096 from rhodecode.model.gist import GistModel
4096 from rhodecode.model.gist import GistModel
4097 return GistModel().get_url(self)
4097 return GistModel().get_url(self)
4098
4098
4099 @classmethod
4099 @classmethod
4100 def base_path(cls):
4100 def base_path(cls):
4101 """
4101 """
4102 Returns base path when all gists are stored
4102 Returns base path when all gists are stored
4103
4103
4104 :param cls:
4104 :param cls:
4105 """
4105 """
4106 from rhodecode.model.gist import GIST_STORE_LOC
4106 from rhodecode.model.gist import GIST_STORE_LOC
4107 q = Session().query(RhodeCodeUi)\
4107 q = Session().query(RhodeCodeUi)\
4108 .filter(RhodeCodeUi.ui_key == URL_SEP)
4108 .filter(RhodeCodeUi.ui_key == URL_SEP)
4109 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4109 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4110 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4110 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4111
4111
4112 def get_api_data(self):
4112 def get_api_data(self):
4113 """
4113 """
4114 Common function for generating gist related data for API
4114 Common function for generating gist related data for API
4115 """
4115 """
4116 gist = self
4116 gist = self
4117 data = {
4117 data = {
4118 'gist_id': gist.gist_id,
4118 'gist_id': gist.gist_id,
4119 'type': gist.gist_type,
4119 'type': gist.gist_type,
4120 'access_id': gist.gist_access_id,
4120 'access_id': gist.gist_access_id,
4121 'description': gist.gist_description,
4121 'description': gist.gist_description,
4122 'url': gist.gist_url(),
4122 'url': gist.gist_url(),
4123 'expires': gist.gist_expires,
4123 'expires': gist.gist_expires,
4124 'created_on': gist.created_on,
4124 'created_on': gist.created_on,
4125 'modified_at': gist.modified_at,
4125 'modified_at': gist.modified_at,
4126 'content': None,
4126 'content': None,
4127 'acl_level': gist.acl_level,
4127 'acl_level': gist.acl_level,
4128 }
4128 }
4129 return data
4129 return data
4130
4130
4131 def __json__(self):
4131 def __json__(self):
4132 data = dict(
4132 data = dict(
4133 )
4133 )
4134 data.update(self.get_api_data())
4134 data.update(self.get_api_data())
4135 return data
4135 return data
4136 # SCM functions
4136 # SCM functions
4137
4137
4138 def scm_instance(self, **kwargs):
4138 def scm_instance(self, **kwargs):
4139 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4139 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4140 return get_vcs_instance(
4140 return get_vcs_instance(
4141 repo_path=safe_str(full_repo_path), create=False)
4141 repo_path=safe_str(full_repo_path), create=False)
4142
4142
4143
4143
4144 class ExternalIdentity(Base, BaseModel):
4144 class ExternalIdentity(Base, BaseModel):
4145 __tablename__ = 'external_identities'
4145 __tablename__ = 'external_identities'
4146 __table_args__ = (
4146 __table_args__ = (
4147 Index('local_user_id_idx', 'local_user_id'),
4147 Index('local_user_id_idx', 'local_user_id'),
4148 Index('external_id_idx', 'external_id'),
4148 Index('external_id_idx', 'external_id'),
4149 base_table_args
4149 base_table_args
4150 )
4150 )
4151
4151
4152 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4152 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4153 external_username = Column('external_username', Unicode(1024), default=u'')
4153 external_username = Column('external_username', Unicode(1024), default=u'')
4154 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4154 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4155 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4155 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4156 access_token = Column('access_token', String(1024), default=u'')
4156 access_token = Column('access_token', String(1024), default=u'')
4157 alt_token = Column('alt_token', String(1024), default=u'')
4157 alt_token = Column('alt_token', String(1024), default=u'')
4158 token_secret = Column('token_secret', String(1024), default=u'')
4158 token_secret = Column('token_secret', String(1024), default=u'')
4159
4159
4160 @classmethod
4160 @classmethod
4161 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4161 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4162 """
4162 """
4163 Returns ExternalIdentity instance based on search params
4163 Returns ExternalIdentity instance based on search params
4164
4164
4165 :param external_id:
4165 :param external_id:
4166 :param provider_name:
4166 :param provider_name:
4167 :return: ExternalIdentity
4167 :return: ExternalIdentity
4168 """
4168 """
4169 query = cls.query()
4169 query = cls.query()
4170 query = query.filter(cls.external_id == external_id)
4170 query = query.filter(cls.external_id == external_id)
4171 query = query.filter(cls.provider_name == provider_name)
4171 query = query.filter(cls.provider_name == provider_name)
4172 if local_user_id:
4172 if local_user_id:
4173 query = query.filter(cls.local_user_id == local_user_id)
4173 query = query.filter(cls.local_user_id == local_user_id)
4174 return query.first()
4174 return query.first()
4175
4175
4176 @classmethod
4176 @classmethod
4177 def user_by_external_id_and_provider(cls, external_id, provider_name):
4177 def user_by_external_id_and_provider(cls, external_id, provider_name):
4178 """
4178 """
4179 Returns User instance based on search params
4179 Returns User instance based on search params
4180
4180
4181 :param external_id:
4181 :param external_id:
4182 :param provider_name:
4182 :param provider_name:
4183 :return: User
4183 :return: User
4184 """
4184 """
4185 query = User.query()
4185 query = User.query()
4186 query = query.filter(cls.external_id == external_id)
4186 query = query.filter(cls.external_id == external_id)
4187 query = query.filter(cls.provider_name == provider_name)
4187 query = query.filter(cls.provider_name == provider_name)
4188 query = query.filter(User.user_id == cls.local_user_id)
4188 query = query.filter(User.user_id == cls.local_user_id)
4189 return query.first()
4189 return query.first()
4190
4190
4191 @classmethod
4191 @classmethod
4192 def by_local_user_id(cls, local_user_id):
4192 def by_local_user_id(cls, local_user_id):
4193 """
4193 """
4194 Returns all tokens for user
4194 Returns all tokens for user
4195
4195
4196 :param local_user_id:
4196 :param local_user_id:
4197 :return: ExternalIdentity
4197 :return: ExternalIdentity
4198 """
4198 """
4199 query = cls.query()
4199 query = cls.query()
4200 query = query.filter(cls.local_user_id == local_user_id)
4200 query = query.filter(cls.local_user_id == local_user_id)
4201 return query
4201 return query
4202
4202
4203 @classmethod
4203 @classmethod
4204 def load_provider_plugin(cls, plugin_id):
4204 def load_provider_plugin(cls, plugin_id):
4205 from rhodecode.authentication.base import loadplugin
4205 from rhodecode.authentication.base import loadplugin
4206 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4206 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4207 auth_plugin = loadplugin(_plugin_id)
4207 auth_plugin = loadplugin(_plugin_id)
4208 return auth_plugin
4208 return auth_plugin
4209
4209
4210
4210
4211 class Integration(Base, BaseModel):
4211 class Integration(Base, BaseModel):
4212 __tablename__ = 'integrations'
4212 __tablename__ = 'integrations'
4213 __table_args__ = (
4213 __table_args__ = (
4214 base_table_args
4214 base_table_args
4215 )
4215 )
4216
4216
4217 integration_id = Column('integration_id', Integer(), primary_key=True)
4217 integration_id = Column('integration_id', Integer(), primary_key=True)
4218 integration_type = Column('integration_type', String(255))
4218 integration_type = Column('integration_type', String(255))
4219 enabled = Column('enabled', Boolean(), nullable=False)
4219 enabled = Column('enabled', Boolean(), nullable=False)
4220 name = Column('name', String(255), nullable=False)
4220 name = Column('name', String(255), nullable=False)
4221 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4221 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4222 default=False)
4222 default=False)
4223
4223
4224 settings = Column(
4224 settings = Column(
4225 'settings_json', MutationObj.as_mutable(
4225 'settings_json', MutationObj.as_mutable(
4226 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4226 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4227 repo_id = Column(
4227 repo_id = Column(
4228 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4228 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4229 nullable=True, unique=None, default=None)
4229 nullable=True, unique=None, default=None)
4230 repo = relationship('Repository', lazy='joined')
4230 repo = relationship('Repository', lazy='joined')
4231
4231
4232 repo_group_id = Column(
4232 repo_group_id = Column(
4233 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4233 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4234 nullable=True, unique=None, default=None)
4234 nullable=True, unique=None, default=None)
4235 repo_group = relationship('RepoGroup', lazy='joined')
4235 repo_group = relationship('RepoGroup', lazy='joined')
4236
4236
4237 @property
4237 @property
4238 def scope(self):
4238 def scope(self):
4239 if self.repo:
4239 if self.repo:
4240 return repr(self.repo)
4240 return repr(self.repo)
4241 if self.repo_group:
4241 if self.repo_group:
4242 if self.child_repos_only:
4242 if self.child_repos_only:
4243 return repr(self.repo_group) + ' (child repos only)'
4243 return repr(self.repo_group) + ' (child repos only)'
4244 else:
4244 else:
4245 return repr(self.repo_group) + ' (recursive)'
4245 return repr(self.repo_group) + ' (recursive)'
4246 if self.child_repos_only:
4246 if self.child_repos_only:
4247 return 'root_repos'
4247 return 'root_repos'
4248 return 'global'
4248 return 'global'
4249
4249
4250 def __repr__(self):
4250 def __repr__(self):
4251 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4251 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4252
4252
4253
4253
4254 class RepoReviewRuleUser(Base, BaseModel):
4254 class RepoReviewRuleUser(Base, BaseModel):
4255 __tablename__ = 'repo_review_rules_users'
4255 __tablename__ = 'repo_review_rules_users'
4256 __table_args__ = (
4256 __table_args__ = (
4257 base_table_args
4257 base_table_args
4258 )
4258 )
4259
4259
4260 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4260 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4261 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4261 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4262 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4262 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4263 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4263 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4264 user = relationship('User')
4264 user = relationship('User')
4265
4265
4266 def rule_data(self):
4266 def rule_data(self):
4267 return {
4267 return {
4268 'mandatory': self.mandatory
4268 'mandatory': self.mandatory
4269 }
4269 }
4270
4270
4271
4271
4272 class RepoReviewRuleUserGroup(Base, BaseModel):
4272 class RepoReviewRuleUserGroup(Base, BaseModel):
4273 __tablename__ = 'repo_review_rules_users_groups'
4273 __tablename__ = 'repo_review_rules_users_groups'
4274 __table_args__ = (
4274 __table_args__ = (
4275 base_table_args
4275 base_table_args
4276 )
4276 )
4277
4277
4278 VOTE_RULE_ALL = -1
4278 VOTE_RULE_ALL = -1
4279
4279
4280 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4280 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4281 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4281 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4282 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4282 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4283 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4283 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4284 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4284 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4285 users_group = relationship('UserGroup')
4285 users_group = relationship('UserGroup')
4286
4286
4287 def rule_data(self):
4287 def rule_data(self):
4288 return {
4288 return {
4289 'mandatory': self.mandatory,
4289 'mandatory': self.mandatory,
4290 'vote_rule': self.vote_rule
4290 'vote_rule': self.vote_rule
4291 }
4291 }
4292
4292
4293 @property
4293 @property
4294 def vote_rule_label(self):
4294 def vote_rule_label(self):
4295 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4295 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4296 return 'all must vote'
4296 return 'all must vote'
4297 else:
4297 else:
4298 return 'min. vote {}'.format(self.vote_rule)
4298 return 'min. vote {}'.format(self.vote_rule)
4299
4299
4300
4300
4301 class RepoReviewRule(Base, BaseModel):
4301 class RepoReviewRule(Base, BaseModel):
4302 __tablename__ = 'repo_review_rules'
4302 __tablename__ = 'repo_review_rules'
4303 __table_args__ = (
4303 __table_args__ = (
4304 base_table_args
4304 base_table_args
4305 )
4305 )
4306
4306
4307 repo_review_rule_id = Column(
4307 repo_review_rule_id = Column(
4308 'repo_review_rule_id', Integer(), primary_key=True)
4308 'repo_review_rule_id', Integer(), primary_key=True)
4309 repo_id = Column(
4309 repo_id = Column(
4310 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4310 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4311 repo = relationship('Repository', backref='review_rules')
4311 repo = relationship('Repository', backref='review_rules')
4312
4312
4313 review_rule_name = Column('review_rule_name', String(255))
4313 review_rule_name = Column('review_rule_name', String(255))
4314 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4314 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4315 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4315 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4316 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4316 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4317
4317
4318 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4318 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4319 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4319 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4320 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4320 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4321 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4321 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4322
4322
4323 rule_users = relationship('RepoReviewRuleUser')
4323 rule_users = relationship('RepoReviewRuleUser')
4324 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4324 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4325
4325
4326 def _validate_pattern(self, value):
4326 def _validate_pattern(self, value):
4327 re.compile('^' + glob2re(value) + '$')
4327 re.compile('^' + glob2re(value) + '$')
4328
4328
4329 @hybrid_property
4329 @hybrid_property
4330 def source_branch_pattern(self):
4330 def source_branch_pattern(self):
4331 return self._branch_pattern or '*'
4331 return self._branch_pattern or '*'
4332
4332
4333 @source_branch_pattern.setter
4333 @source_branch_pattern.setter
4334 def source_branch_pattern(self, value):
4334 def source_branch_pattern(self, value):
4335 self._validate_pattern(value)
4335 self._validate_pattern(value)
4336 self._branch_pattern = value or '*'
4336 self._branch_pattern = value or '*'
4337
4337
4338 @hybrid_property
4338 @hybrid_property
4339 def target_branch_pattern(self):
4339 def target_branch_pattern(self):
4340 return self._target_branch_pattern or '*'
4340 return self._target_branch_pattern or '*'
4341
4341
4342 @target_branch_pattern.setter
4342 @target_branch_pattern.setter
4343 def target_branch_pattern(self, value):
4343 def target_branch_pattern(self, value):
4344 self._validate_pattern(value)
4344 self._validate_pattern(value)
4345 self._target_branch_pattern = value or '*'
4345 self._target_branch_pattern = value or '*'
4346
4346
4347 @hybrid_property
4347 @hybrid_property
4348 def file_pattern(self):
4348 def file_pattern(self):
4349 return self._file_pattern or '*'
4349 return self._file_pattern or '*'
4350
4350
4351 @file_pattern.setter
4351 @file_pattern.setter
4352 def file_pattern(self, value):
4352 def file_pattern(self, value):
4353 self._validate_pattern(value)
4353 self._validate_pattern(value)
4354 self._file_pattern = value or '*'
4354 self._file_pattern = value or '*'
4355
4355
4356 def matches(self, source_branch, target_branch, files_changed):
4356 def matches(self, source_branch, target_branch, files_changed):
4357 """
4357 """
4358 Check if this review rule matches a branch/files in a pull request
4358 Check if this review rule matches a branch/files in a pull request
4359
4359
4360 :param source_branch: source branch name for the commit
4360 :param source_branch: source branch name for the commit
4361 :param target_branch: target branch name for the commit
4361 :param target_branch: target branch name for the commit
4362 :param files_changed: list of file paths changed in the pull request
4362 :param files_changed: list of file paths changed in the pull request
4363 """
4363 """
4364
4364
4365 source_branch = source_branch or ''
4365 source_branch = source_branch or ''
4366 target_branch = target_branch or ''
4366 target_branch = target_branch or ''
4367 files_changed = files_changed or []
4367 files_changed = files_changed or []
4368
4368
4369 branch_matches = True
4369 branch_matches = True
4370 if source_branch or target_branch:
4370 if source_branch or target_branch:
4371 if self.source_branch_pattern == '*':
4371 if self.source_branch_pattern == '*':
4372 source_branch_match = True
4372 source_branch_match = True
4373 else:
4373 else:
4374 if self.source_branch_pattern.startswith('re:'):
4374 if self.source_branch_pattern.startswith('re:'):
4375 source_pattern = self.source_branch_pattern[3:]
4375 source_pattern = self.source_branch_pattern[3:]
4376 else:
4376 else:
4377 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4377 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4378 source_branch_regex = re.compile(source_pattern)
4378 source_branch_regex = re.compile(source_pattern)
4379 source_branch_match = bool(source_branch_regex.search(source_branch))
4379 source_branch_match = bool(source_branch_regex.search(source_branch))
4380 if self.target_branch_pattern == '*':
4380 if self.target_branch_pattern == '*':
4381 target_branch_match = True
4381 target_branch_match = True
4382 else:
4382 else:
4383 if self.target_branch_pattern.startswith('re:'):
4383 if self.target_branch_pattern.startswith('re:'):
4384 target_pattern = self.target_branch_pattern[3:]
4384 target_pattern = self.target_branch_pattern[3:]
4385 else:
4385 else:
4386 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4386 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4387 target_branch_regex = re.compile(target_pattern)
4387 target_branch_regex = re.compile(target_pattern)
4388 target_branch_match = bool(target_branch_regex.search(target_branch))
4388 target_branch_match = bool(target_branch_regex.search(target_branch))
4389
4389
4390 branch_matches = source_branch_match and target_branch_match
4390 branch_matches = source_branch_match and target_branch_match
4391
4391
4392 files_matches = True
4392 files_matches = True
4393 if self.file_pattern != '*':
4393 if self.file_pattern != '*':
4394 files_matches = False
4394 files_matches = False
4395 if self.file_pattern.startswith('re:'):
4395 if self.file_pattern.startswith('re:'):
4396 file_pattern = self.file_pattern[3:]
4396 file_pattern = self.file_pattern[3:]
4397 else:
4397 else:
4398 file_pattern = glob2re(self.file_pattern)
4398 file_pattern = glob2re(self.file_pattern)
4399 file_regex = re.compile(file_pattern)
4399 file_regex = re.compile(file_pattern)
4400 for filename in files_changed:
4400 for filename in files_changed:
4401 if file_regex.search(filename):
4401 if file_regex.search(filename):
4402 files_matches = True
4402 files_matches = True
4403 break
4403 break
4404
4404
4405 return branch_matches and files_matches
4405 return branch_matches and files_matches
4406
4406
4407 @property
4407 @property
4408 def review_users(self):
4408 def review_users(self):
4409 """ Returns the users which this rule applies to """
4409 """ Returns the users which this rule applies to """
4410
4410
4411 users = collections.OrderedDict()
4411 users = collections.OrderedDict()
4412
4412
4413 for rule_user in self.rule_users:
4413 for rule_user in self.rule_users:
4414 if rule_user.user.active:
4414 if rule_user.user.active:
4415 if rule_user.user not in users:
4415 if rule_user.user not in users:
4416 users[rule_user.user.username] = {
4416 users[rule_user.user.username] = {
4417 'user': rule_user.user,
4417 'user': rule_user.user,
4418 'source': 'user',
4418 'source': 'user',
4419 'source_data': {},
4419 'source_data': {},
4420 'data': rule_user.rule_data()
4420 'data': rule_user.rule_data()
4421 }
4421 }
4422
4422
4423 for rule_user_group in self.rule_user_groups:
4423 for rule_user_group in self.rule_user_groups:
4424 source_data = {
4424 source_data = {
4425 'user_group_id': rule_user_group.users_group.users_group_id,
4425 'user_group_id': rule_user_group.users_group.users_group_id,
4426 'name': rule_user_group.users_group.users_group_name,
4426 'name': rule_user_group.users_group.users_group_name,
4427 'members': len(rule_user_group.users_group.members)
4427 'members': len(rule_user_group.users_group.members)
4428 }
4428 }
4429 for member in rule_user_group.users_group.members:
4429 for member in rule_user_group.users_group.members:
4430 if member.user.active:
4430 if member.user.active:
4431 key = member.user.username
4431 key = member.user.username
4432 if key in users:
4432 if key in users:
4433 # skip this member as we have him already
4433 # skip this member as we have him already
4434 # this prevents from override the "first" matched
4434 # this prevents from override the "first" matched
4435 # users with duplicates in multiple groups
4435 # users with duplicates in multiple groups
4436 continue
4436 continue
4437
4437
4438 users[key] = {
4438 users[key] = {
4439 'user': member.user,
4439 'user': member.user,
4440 'source': 'user_group',
4440 'source': 'user_group',
4441 'source_data': source_data,
4441 'source_data': source_data,
4442 'data': rule_user_group.rule_data()
4442 'data': rule_user_group.rule_data()
4443 }
4443 }
4444
4444
4445 return users
4445 return users
4446
4446
4447 def user_group_vote_rule(self, user_id):
4447 def user_group_vote_rule(self, user_id):
4448
4448
4449 rules = []
4449 rules = []
4450 if not self.rule_user_groups:
4450 if not self.rule_user_groups:
4451 return rules
4451 return rules
4452
4452
4453 for user_group in self.rule_user_groups:
4453 for user_group in self.rule_user_groups:
4454 user_group_members = [x.user_id for x in user_group.users_group.members]
4454 user_group_members = [x.user_id for x in user_group.users_group.members]
4455 if user_id in user_group_members:
4455 if user_id in user_group_members:
4456 rules.append(user_group)
4456 rules.append(user_group)
4457 return rules
4457 return rules
4458
4458
4459 def __repr__(self):
4459 def __repr__(self):
4460 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4460 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4461 self.repo_review_rule_id, self.repo)
4461 self.repo_review_rule_id, self.repo)
4462
4462
4463
4463
4464 class ScheduleEntry(Base, BaseModel):
4464 class ScheduleEntry(Base, BaseModel):
4465 __tablename__ = 'schedule_entries'
4465 __tablename__ = 'schedule_entries'
4466 __table_args__ = (
4466 __table_args__ = (
4467 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4467 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4468 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4468 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4469 base_table_args,
4469 base_table_args,
4470 )
4470 )
4471
4471
4472 schedule_types = ['crontab', 'timedelta', 'integer']
4472 schedule_types = ['crontab', 'timedelta', 'integer']
4473 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4473 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4474
4474
4475 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4475 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4476 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4476 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4477 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4477 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4478
4478
4479 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4479 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4480 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4480 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4481
4481
4482 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4482 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4483 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4483 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4484
4484
4485 # task
4485 # task
4486 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4486 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4487 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4487 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4488 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4488 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4489 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4489 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4490
4490
4491 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4491 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4492 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4492 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4493
4493
4494 @hybrid_property
4494 @hybrid_property
4495 def schedule_type(self):
4495 def schedule_type(self):
4496 return self._schedule_type
4496 return self._schedule_type
4497
4497
4498 @schedule_type.setter
4498 @schedule_type.setter
4499 def schedule_type(self, val):
4499 def schedule_type(self, val):
4500 if val not in self.schedule_types:
4500 if val not in self.schedule_types:
4501 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4501 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4502 val, self.schedule_type))
4502 val, self.schedule_type))
4503
4503
4504 self._schedule_type = val
4504 self._schedule_type = val
4505
4505
4506 @classmethod
4506 @classmethod
4507 def get_uid(cls, obj):
4507 def get_uid(cls, obj):
4508 args = obj.task_args
4508 args = obj.task_args
4509 kwargs = obj.task_kwargs
4509 kwargs = obj.task_kwargs
4510 if isinstance(args, JsonRaw):
4510 if isinstance(args, JsonRaw):
4511 try:
4511 try:
4512 args = json.loads(args)
4512 args = json.loads(args)
4513 except ValueError:
4513 except ValueError:
4514 args = tuple()
4514 args = tuple()
4515
4515
4516 if isinstance(kwargs, JsonRaw):
4516 if isinstance(kwargs, JsonRaw):
4517 try:
4517 try:
4518 kwargs = json.loads(kwargs)
4518 kwargs = json.loads(kwargs)
4519 except ValueError:
4519 except ValueError:
4520 kwargs = dict()
4520 kwargs = dict()
4521
4521
4522 dot_notation = obj.task_dot_notation
4522 dot_notation = obj.task_dot_notation
4523 val = '.'.join(map(safe_str, [
4523 val = '.'.join(map(safe_str, [
4524 sorted(dot_notation), args, sorted(kwargs.items())]))
4524 sorted(dot_notation), args, sorted(kwargs.items())]))
4525 return hashlib.sha1(val).hexdigest()
4525 return hashlib.sha1(val).hexdigest()
4526
4526
4527 @classmethod
4527 @classmethod
4528 def get_by_schedule_name(cls, schedule_name):
4528 def get_by_schedule_name(cls, schedule_name):
4529 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4529 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4530
4530
4531 @classmethod
4531 @classmethod
4532 def get_by_schedule_id(cls, schedule_id):
4532 def get_by_schedule_id(cls, schedule_id):
4533 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4533 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4534
4534
4535 @property
4535 @property
4536 def task(self):
4536 def task(self):
4537 return self.task_dot_notation
4537 return self.task_dot_notation
4538
4538
4539 @property
4539 @property
4540 def schedule(self):
4540 def schedule(self):
4541 from rhodecode.lib.celerylib.utils import raw_2_schedule
4541 from rhodecode.lib.celerylib.utils import raw_2_schedule
4542 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4542 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4543 return schedule
4543 return schedule
4544
4544
4545 @property
4545 @property
4546 def args(self):
4546 def args(self):
4547 try:
4547 try:
4548 return list(self.task_args or [])
4548 return list(self.task_args or [])
4549 except ValueError:
4549 except ValueError:
4550 return list()
4550 return list()
4551
4551
4552 @property
4552 @property
4553 def kwargs(self):
4553 def kwargs(self):
4554 try:
4554 try:
4555 return dict(self.task_kwargs or {})
4555 return dict(self.task_kwargs or {})
4556 except ValueError:
4556 except ValueError:
4557 return dict()
4557 return dict()
4558
4558
4559 def _as_raw(self, val):
4559 def _as_raw(self, val):
4560 if hasattr(val, 'de_coerce'):
4560 if hasattr(val, 'de_coerce'):
4561 val = val.de_coerce()
4561 val = val.de_coerce()
4562 if val:
4562 if val:
4563 val = json.dumps(val)
4563 val = json.dumps(val)
4564
4564
4565 return val
4565 return val
4566
4566
4567 @property
4567 @property
4568 def schedule_definition_raw(self):
4568 def schedule_definition_raw(self):
4569 return self._as_raw(self.schedule_definition)
4569 return self._as_raw(self.schedule_definition)
4570
4570
4571 @property
4571 @property
4572 def args_raw(self):
4572 def args_raw(self):
4573 return self._as_raw(self.task_args)
4573 return self._as_raw(self.task_args)
4574
4574
4575 @property
4575 @property
4576 def kwargs_raw(self):
4576 def kwargs_raw(self):
4577 return self._as_raw(self.task_kwargs)
4577 return self._as_raw(self.task_kwargs)
4578
4578
4579 def __repr__(self):
4579 def __repr__(self):
4580 return '<DB:ScheduleEntry({}:{})>'.format(
4580 return '<DB:ScheduleEntry({}:{})>'.format(
4581 self.schedule_entry_id, self.schedule_name)
4581 self.schedule_entry_id, self.schedule_name)
4582
4582
4583
4583
4584 @event.listens_for(ScheduleEntry, 'before_update')
4584 @event.listens_for(ScheduleEntry, 'before_update')
4585 def update_task_uid(mapper, connection, target):
4585 def update_task_uid(mapper, connection, target):
4586 target.task_uid = ScheduleEntry.get_uid(target)
4586 target.task_uid = ScheduleEntry.get_uid(target)
4587
4587
4588
4588
4589 @event.listens_for(ScheduleEntry, 'before_insert')
4589 @event.listens_for(ScheduleEntry, 'before_insert')
4590 def set_task_uid(mapper, connection, target):
4590 def set_task_uid(mapper, connection, target):
4591 target.task_uid = ScheduleEntry.get_uid(target)
4591 target.task_uid = ScheduleEntry.get_uid(target)
4592
4592
4593
4593
4594 class _BaseBranchPerms(BaseModel):
4594 class _BaseBranchPerms(BaseModel):
4595 @classmethod
4595 @classmethod
4596 def compute_hash(cls, value):
4596 def compute_hash(cls, value):
4597 return sha1_safe(value)
4597 return sha1_safe(value)
4598
4598
4599 @hybrid_property
4599 @hybrid_property
4600 def branch_pattern(self):
4600 def branch_pattern(self):
4601 return self._branch_pattern or '*'
4601 return self._branch_pattern or '*'
4602
4602
4603 @hybrid_property
4603 @hybrid_property
4604 def branch_hash(self):
4604 def branch_hash(self):
4605 return self._branch_hash
4605 return self._branch_hash
4606
4606
4607 def _validate_glob(self, value):
4607 def _validate_glob(self, value):
4608 re.compile('^' + glob2re(value) + '$')
4608 re.compile('^' + glob2re(value) + '$')
4609
4609
4610 @branch_pattern.setter
4610 @branch_pattern.setter
4611 def branch_pattern(self, value):
4611 def branch_pattern(self, value):
4612 self._validate_glob(value)
4612 self._validate_glob(value)
4613 self._branch_pattern = value or '*'
4613 self._branch_pattern = value or '*'
4614 # set the Hash when setting the branch pattern
4614 # set the Hash when setting the branch pattern
4615 self._branch_hash = self.compute_hash(self._branch_pattern)
4615 self._branch_hash = self.compute_hash(self._branch_pattern)
4616
4616
4617 def matches(self, branch):
4617 def matches(self, branch):
4618 """
4618 """
4619 Check if this the branch matches entry
4619 Check if this the branch matches entry
4620
4620
4621 :param branch: branch name for the commit
4621 :param branch: branch name for the commit
4622 """
4622 """
4623
4623
4624 branch = branch or ''
4624 branch = branch or ''
4625
4625
4626 branch_matches = True
4626 branch_matches = True
4627 if branch:
4627 if branch:
4628 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4628 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4629 branch_matches = bool(branch_regex.search(branch))
4629 branch_matches = bool(branch_regex.search(branch))
4630
4630
4631 return branch_matches
4631 return branch_matches
4632
4632
4633
4633
4634 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4634 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4635 __tablename__ = 'user_to_repo_branch_permissions'
4635 __tablename__ = 'user_to_repo_branch_permissions'
4636 __table_args__ = (
4636 __table_args__ = (
4637 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4637 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4638 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4638 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4639 )
4639 )
4640
4640
4641 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4641 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4642
4642
4643 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4643 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4644 repo = relationship('Repository', backref='user_branch_perms')
4644 repo = relationship('Repository', backref='user_branch_perms')
4645
4645
4646 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4646 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4647 permission = relationship('Permission')
4647 permission = relationship('Permission')
4648
4648
4649 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4649 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4650 user_repo_to_perm = relationship('UserRepoToPerm')
4650 user_repo_to_perm = relationship('UserRepoToPerm')
4651
4651
4652 rule_order = Column('rule_order', Integer(), nullable=False)
4652 rule_order = Column('rule_order', Integer(), nullable=False)
4653 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4653 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4654 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4654 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4655
4655
4656 def __unicode__(self):
4656 def __unicode__(self):
4657 return u'<UserBranchPermission(%s => %r)>' % (
4657 return u'<UserBranchPermission(%s => %r)>' % (
4658 self.user_repo_to_perm, self.branch_pattern)
4658 self.user_repo_to_perm, self.branch_pattern)
4659
4659
4660
4660
4661 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4661 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4662 __tablename__ = 'user_group_to_repo_branch_permissions'
4662 __tablename__ = 'user_group_to_repo_branch_permissions'
4663 __table_args__ = (
4663 __table_args__ = (
4664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4665 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4665 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4666 )
4666 )
4667
4667
4668 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4668 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4669
4669
4670 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4670 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4671 repo = relationship('Repository', backref='user_group_branch_perms')
4671 repo = relationship('Repository', backref='user_group_branch_perms')
4672
4672
4673 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4673 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4674 permission = relationship('Permission')
4674 permission = relationship('Permission')
4675
4675
4676 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4676 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4677 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4677 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4678
4678
4679 rule_order = Column('rule_order', Integer(), nullable=False)
4679 rule_order = Column('rule_order', Integer(), nullable=False)
4680 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4680 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4681 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4681 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4682
4682
4683 def __unicode__(self):
4683 def __unicode__(self):
4684 return u'<UserBranchPermission(%s => %r)>' % (
4684 return u'<UserBranchPermission(%s => %r)>' % (
4685 self.user_group_repo_to_perm, self.branch_pattern)
4685 self.user_group_repo_to_perm, self.branch_pattern)
4686
4686
4687
4687
4688 class DbMigrateVersion(Base, BaseModel):
4688 class DbMigrateVersion(Base, BaseModel):
4689 __tablename__ = 'db_migrate_version'
4689 __tablename__ = 'db_migrate_version'
4690 __table_args__ = (
4690 __table_args__ = (
4691 base_table_args,
4691 base_table_args,
4692 )
4692 )
4693
4693
4694 repository_id = Column('repository_id', String(250), primary_key=True)
4694 repository_id = Column('repository_id', String(250), primary_key=True)
4695 repository_path = Column('repository_path', Text)
4695 repository_path = Column('repository_path', Text)
4696 version = Column('version', Integer)
4696 version = Column('version', Integer)
4697
4697
4698 @classmethod
4698 @classmethod
4699 def set_version(cls, version):
4699 def set_version(cls, version):
4700 """
4700 """
4701 Helper for forcing a different version, usually for debugging purposes via ishell.
4701 Helper for forcing a different version, usually for debugging purposes via ishell.
4702 """
4702 """
4703 ver = DbMigrateVersion.query().first()
4703 ver = DbMigrateVersion.query().first()
4704 ver.version = version
4704 ver.version = version
4705 Session().commit()
4705 Session().commit()
4706
4706
4707
4707
4708 class DbSession(Base, BaseModel):
4708 class DbSession(Base, BaseModel):
4709 __tablename__ = 'db_session'
4709 __tablename__ = 'db_session'
4710 __table_args__ = (
4710 __table_args__ = (
4711 base_table_args,
4711 base_table_args,
4712 )
4712 )
4713
4713
4714 def __repr__(self):
4714 def __repr__(self):
4715 return '<DB:DbSession({})>'.format(self.id)
4715 return '<DB:DbSession({})>'.format(self.id)
4716
4716
4717 id = Column('id', Integer())
4717 id = Column('id', Integer())
4718 namespace = Column('namespace', String(255), primary_key=True)
4718 namespace = Column('namespace', String(255), primary_key=True)
4719 accessed = Column('accessed', DateTime, nullable=False)
4719 accessed = Column('accessed', DateTime, nullable=False)
4720 created = Column('created', DateTime, nullable=False)
4720 created = Column('created', DateTime, nullable=False)
4721 data = Column('data', PickleType, nullable=False)
4721 data = Column('data', PickleType, nullable=False)
@@ -1,24 +1,24 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22
22
23 from colander import Invalid # noqa, don't remove this
23 from colander import Invalid # pragma: no cover
24
24
@@ -1,1025 +1,1024 b''
1 # (c) 2005 Ian Bicking and contributors; written for Paste
1 # (c) 2005 Ian Bicking and contributors; written for Paste
2 # (http://pythonpaste.org) Licensed under the MIT license:
2 # (http://pythonpaste.org) Licensed under the MIT license:
3 # http://www.opensource.org/licenses/mit-license.php
3 # http://www.opensource.org/licenses/mit-license.php
4 #
4 #
5 # For discussion of daemonizing:
5 # For discussion of daemonizing:
6 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
6 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
7 #
7 #
8 # Code taken also from QP: http://www.mems-exchange.org/software/qp/ From
8 # Code taken also from QP: http://www.mems-exchange.org/software/qp/ From
9 # lib/site.py
9 # lib/site.py
10
10
11 import atexit
11 import atexit
12 import errno
12 import errno
13 import fnmatch
13 import fnmatch
14 import logging
14 import logging
15 import optparse
15 import optparse
16 import os
16 import os
17 import re
17 import re
18 import subprocess32
18 import subprocess32
19 import sys
19 import sys
20 import textwrap
20 import textwrap
21 import threading
21 import threading
22 import time
22 import time
23 import traceback
23 import traceback
24
24
25 from logging.config import fileConfig
25 from logging.config import fileConfig
26 import ConfigParser as configparser
26 import ConfigParser as configparser
27 from paste.deploy import loadserver
27 from paste.deploy import loadserver
28 from paste.deploy import loadapp
28 from paste.deploy import loadapp
29
29
30 import rhodecode
30 import rhodecode
31 from rhodecode.lib.compat import kill
31 from rhodecode.lib.compat import kill
32
32
33
33
34 def make_web_build_callback(filename):
34 def make_web_build_callback(filename):
35 p = subprocess32.Popen('make web-build', shell=True,
35 p = subprocess32.Popen('make web-build', shell=True,
36 stdout=subprocess32.PIPE,
36 stdout=subprocess32.PIPE,
37 stderr=subprocess32.PIPE,
37 stderr=subprocess32.PIPE,
38 cwd=os.path.dirname(os.path.dirname(__file__)))
38 cwd=os.path.dirname(os.path.dirname(__file__)))
39 stdout, stderr = p.communicate()
39 stdout, stderr = p.communicate()
40 stdout = ''.join(stdout)
40 stdout = ''.join(stdout)
41 stderr = ''.join(stderr)
41 stderr = ''.join(stderr)
42 if stdout:
42 if stdout:
43 print(stdout)
43 print(stdout)
44 if stderr:
44 if stderr:
45 print('%s %s %s' % ('-' * 20, 'ERRORS', '-' * 20))
45 print('%s %s %s' % ('-' * 20, 'ERRORS', '-' * 20))
46 print(stderr)
46 print(stderr)
47
47
48
48
49 MAXFD = 1024
49 MAXFD = 1024
50 HERE = os.path.dirname(os.path.abspath(__file__))
50 HERE = os.path.dirname(os.path.abspath(__file__))
51 SERVER_RUNNING_FILE = None
51 SERVER_RUNNING_FILE = None
52
52
53
53
54 # watch those extra files for changes, server gets restarted if file changes
54 # watch those extra files for changes, server gets restarted if file changes
55 GLOBAL_EXTRA_FILES = {
55 GLOBAL_EXTRA_FILES = {
56 'rhodecode/public/css/*.less': make_web_build_callback,
56 'rhodecode/public/css/*.less': make_web_build_callback,
57 'rhodecode/public/js/src/**/*.js': make_web_build_callback,
57 'rhodecode/public/js/src/**/*.js': make_web_build_callback,
58 }
58 }
59
59
60
60
61
61
62 ## HOOKS - inspired by gunicorn #
62 ## HOOKS - inspired by gunicorn #
63
63
64 def when_ready(server):
64 def when_ready(server):
65 """
65 """
66 Called just after the server is started.
66 Called just after the server is started.
67 """
67 """
68
68
69 def _remove_server_running_file():
69 def _remove_server_running_file():
70 if os.path.isfile(SERVER_RUNNING_FILE):
70 if os.path.isfile(SERVER_RUNNING_FILE):
71 os.remove(SERVER_RUNNING_FILE)
71 os.remove(SERVER_RUNNING_FILE)
72
72
73 if SERVER_RUNNING_FILE:
73 if SERVER_RUNNING_FILE:
74 with open(SERVER_RUNNING_FILE, 'wb') as f:
74 with open(SERVER_RUNNING_FILE, 'wb') as f:
75 f.write(str(os.getpid()))
75 f.write(str(os.getpid()))
76 # register cleanup of that file when server exits
76 # register cleanup of that file when server exits
77 atexit.register(_remove_server_running_file)
77 atexit.register(_remove_server_running_file)
78
78
79
79
80 def setup_logging(config_uri, fileConfig=fileConfig,
80 def setup_logging(config_uri, fileConfig=fileConfig,
81 configparser=configparser):
81 configparser=configparser):
82 """
82 """
83 Set up logging via the logging module's fileConfig function with the
83 Set up logging via the logging module's fileConfig function with the
84 filename specified via ``config_uri`` (a string in the form
84 filename specified via ``config_uri`` (a string in the form
85 ``filename#sectionname``).
85 ``filename#sectionname``).
86
86
87 ConfigParser defaults are specified for the special ``__file__``
87 ConfigParser defaults are specified for the special ``__file__``
88 and ``here`` variables, similar to PasteDeploy config loading.
88 and ``here`` variables, similar to PasteDeploy config loading.
89 """
89 """
90 path, _ = _getpathsec(config_uri, None)
90 path, _ = _getpathsec(config_uri, None)
91 parser = configparser.ConfigParser()
91 parser = configparser.ConfigParser()
92 parser.read([path])
92 parser.read([path])
93 if parser.has_section('loggers'):
93 if parser.has_section('loggers'):
94 config_file = os.path.abspath(path)
94 config_file = os.path.abspath(path)
95 return fileConfig(
95 return fileConfig(
96 config_file,
96 config_file,
97 {'__file__': config_file, 'here': os.path.dirname(config_file)}
97 {'__file__': config_file, 'here': os.path.dirname(config_file)}
98 )
98 )
99
99
100
100
101 def set_rhodecode_is_test(config_uri):
101 def set_rhodecode_is_test(config_uri):
102 """If is_test is defined in the config file sets rhodecode.is_test."""
102 """If is_test is defined in the config file sets rhodecode.is_test."""
103 path, _ = _getpathsec(config_uri, None)
103 path, _ = _getpathsec(config_uri, None)
104 parser = configparser.ConfigParser()
104 parser = configparser.ConfigParser()
105 parser.read(path)
105 parser.read(path)
106 rhodecode.is_test = (
106 rhodecode.is_test = (
107 parser.has_option('app:main', 'is_test') and
107 parser.has_option('app:main', 'is_test') and
108 parser.getboolean('app:main', 'is_test'))
108 parser.getboolean('app:main', 'is_test'))
109
109
110
110
111 def _getpathsec(config_uri, name):
111 def _getpathsec(config_uri, name):
112 if '#' in config_uri:
112 if '#' in config_uri:
113 path, section = config_uri.split('#', 1)
113 path, section = config_uri.split('#', 1)
114 else:
114 else:
115 path, section = config_uri, 'main'
115 path, section = config_uri, 'main'
116 if name:
116 if name:
117 section = name
117 section = name
118 return path, section
118 return path, section
119
119
120
120
121 def parse_vars(args):
121 def parse_vars(args):
122 """
122 """
123 Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
123 Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
124 'b', 'c': 'd'}``
124 'b', 'c': 'd'}``
125 """
125 """
126 result = {}
126 result = {}
127 for arg in args:
127 for arg in args:
128 if '=' not in arg:
128 if '=' not in arg:
129 raise ValueError(
129 raise ValueError(
130 'Variable assignment %r invalid (no "=")'
130 'Variable assignment %r invalid (no "=")'
131 % arg)
131 % arg)
132 name, value = arg.split('=', 1)
132 name, value = arg.split('=', 1)
133 result[name] = value
133 result[name] = value
134 return result
134 return result
135
135
136
136
137 def _match_pattern(filename):
137 def _match_pattern(filename):
138 for pattern in GLOBAL_EXTRA_FILES:
138 for pattern in GLOBAL_EXTRA_FILES:
139 if fnmatch.fnmatch(filename, pattern):
139 if fnmatch.fnmatch(filename, pattern):
140 return pattern
140 return pattern
141 return False
141 return False
142
142
143
143
144 def generate_extra_file_list():
144 def generate_extra_file_list():
145
145
146 extra_list = []
146 extra_list = []
147 for root, dirs, files in os.walk(HERE, topdown=True):
147 for root, dirs, files in os.walk(HERE, topdown=True):
148 for fname in files:
148 for fname in files:
149 stripped_src = os.path.join(
149 stripped_src = os.path.join(
150 'rhodecode', os.path.relpath(os.path.join(root, fname), HERE))
150 'rhodecode', os.path.relpath(os.path.join(root, fname), HERE))
151
151
152 if _match_pattern(stripped_src):
152 if _match_pattern(stripped_src):
153 extra_list.append(stripped_src)
153 extra_list.append(stripped_src)
154
154
155 return extra_list
155 return extra_list
156
156
157
157
158 def run_callback_for_pattern(filename):
158 def run_callback_for_pattern(filename):
159 pattern = _match_pattern(filename)
159 pattern = _match_pattern(filename)
160 if pattern:
160 if pattern:
161 _file_callback = GLOBAL_EXTRA_FILES.get(pattern)
161 _file_callback = GLOBAL_EXTRA_FILES.get(pattern)
162 if callable(_file_callback):
162 if callable(_file_callback):
163 _file_callback(filename)
163 _file_callback(filename)
164
164
165
165
166 class DaemonizeException(Exception):
166 class DaemonizeException(Exception):
167 pass
167 pass
168
168
169
169
170 class RcServerCommand(object):
170 class RcServerCommand(object):
171
171
172 usage = '%prog config_uri [start|stop|restart|status] [var=value]'
172 usage = '%prog config_uri [start|stop|restart|status] [var=value]'
173 description = """\
173 description = """\
174 This command serves a web application that uses a PasteDeploy
174 This command serves a web application that uses a PasteDeploy
175 configuration file for the server and application.
175 configuration file for the server and application.
176
176
177 If start/stop/restart is given, then --daemon is implied, and it will
177 If start/stop/restart is given, then --daemon is implied, and it will
178 start (normal operation), stop (--stop-daemon), or do both.
178 start (normal operation), stop (--stop-daemon), or do both.
179
179
180 You can also include variable assignments like 'http_port=8080'
180 You can also include variable assignments like 'http_port=8080'
181 and then use %(http_port)s in your config files.
181 and then use %(http_port)s in your config files.
182 """
182 """
183 default_verbosity = 1
183 default_verbosity = 1
184
184
185 parser = optparse.OptionParser(
185 parser = optparse.OptionParser(
186 usage,
186 usage,
187 description=textwrap.dedent(description)
187 description=textwrap.dedent(description)
188 )
188 )
189 parser.add_option(
189 parser.add_option(
190 '-n', '--app-name',
190 '-n', '--app-name',
191 dest='app_name',
191 dest='app_name',
192 metavar='NAME',
192 metavar='NAME',
193 help="Load the named application (default main)")
193 help="Load the named application (default main)")
194 parser.add_option(
194 parser.add_option(
195 '-s', '--server',
195 '-s', '--server',
196 dest='server',
196 dest='server',
197 metavar='SERVER_TYPE',
197 metavar='SERVER_TYPE',
198 help="Use the named server.")
198 help="Use the named server.")
199 parser.add_option(
199 parser.add_option(
200 '--server-name',
200 '--server-name',
201 dest='server_name',
201 dest='server_name',
202 metavar='SECTION_NAME',
202 metavar='SECTION_NAME',
203 help=("Use the named server as defined in the configuration file "
203 help=("Use the named server as defined in the configuration file "
204 "(default: main)"))
204 "(default: main)"))
205 parser.add_option(
205 parser.add_option(
206 '--with-vcsserver',
206 '--with-vcsserver',
207 dest='vcs_server',
207 dest='vcs_server',
208 action='store_true',
208 action='store_true',
209 help=("Start the vcsserver instance together with the RhodeCode server"))
209 help=("Start the vcsserver instance together with the RhodeCode server"))
210 if hasattr(os, 'fork'):
210 if hasattr(os, 'fork'):
211 parser.add_option(
211 parser.add_option(
212 '--daemon',
212 '--daemon',
213 dest="daemon",
213 dest="daemon",
214 action="store_true",
214 action="store_true",
215 help="Run in daemon (background) mode")
215 help="Run in daemon (background) mode")
216 parser.add_option(
216 parser.add_option(
217 '--pid-file',
217 '--pid-file',
218 dest='pid_file',
218 dest='pid_file',
219 metavar='FILENAME',
219 metavar='FILENAME',
220 help=("Save PID to file (default to pyramid.pid if running in "
220 help=("Save PID to file (default to pyramid.pid if running in "
221 "daemon mode)"))
221 "daemon mode)"))
222 parser.add_option(
222 parser.add_option(
223 '--running-file',
223 '--running-file',
224 dest='running_file',
224 dest='running_file',
225 metavar='RUNNING_FILE',
225 metavar='RUNNING_FILE',
226 help="Create a running file after the server is initalized with "
226 help="Create a running file after the server is initalized with "
227 "stored PID of process")
227 "stored PID of process")
228 parser.add_option(
228 parser.add_option(
229 '--log-file',
229 '--log-file',
230 dest='log_file',
230 dest='log_file',
231 metavar='LOG_FILE',
231 metavar='LOG_FILE',
232 help="Save output to the given log file (redirects stdout)")
232 help="Save output to the given log file (redirects stdout)")
233 parser.add_option(
233 parser.add_option(
234 '--reload',
234 '--reload',
235 dest='reload',
235 dest='reload',
236 action='store_true',
236 action='store_true',
237 help="Use auto-restart file monitor")
237 help="Use auto-restart file monitor")
238 parser.add_option(
238 parser.add_option(
239 '--reload-interval',
239 '--reload-interval',
240 dest='reload_interval',
240 dest='reload_interval',
241 default=1,
241 default=1,
242 help=("Seconds between checking files (low number can cause "
242 help=("Seconds between checking files (low number can cause "
243 "significant CPU usage)"))
243 "significant CPU usage)"))
244 parser.add_option(
244 parser.add_option(
245 '--monitor-restart',
245 '--monitor-restart',
246 dest='monitor_restart',
246 dest='monitor_restart',
247 action='store_true',
247 action='store_true',
248 help="Auto-restart server if it dies")
248 help="Auto-restart server if it dies")
249 parser.add_option(
249 parser.add_option(
250 '--status',
250 '--status',
251 action='store_true',
251 action='store_true',
252 dest='show_status',
252 dest='show_status',
253 help="Show the status of the (presumably daemonized) server")
253 help="Show the status of the (presumably daemonized) server")
254 parser.add_option(
254 parser.add_option(
255 '-v', '--verbose',
255 '-v', '--verbose',
256 default=default_verbosity,
256 default=default_verbosity,
257 dest='verbose',
257 dest='verbose',
258 action='count',
258 action='count',
259 help="Set verbose level (default "+str(default_verbosity)+")")
259 help="Set verbose level (default "+str(default_verbosity)+")")
260 parser.add_option(
260 parser.add_option(
261 '-q', '--quiet',
261 '-q', '--quiet',
262 action='store_const',
262 action='store_const',
263 const=0,
263 const=0,
264 dest='verbose',
264 dest='verbose',
265 help="Suppress verbose output")
265 help="Suppress verbose output")
266
266
267 if hasattr(os, 'setuid'):
267 if hasattr(os, 'setuid'):
268 # I don't think these are available on Windows
268 # I don't think these are available on Windows
269 parser.add_option(
269 parser.add_option(
270 '--user',
270 '--user',
271 dest='set_user',
271 dest='set_user',
272 metavar="USERNAME",
272 metavar="USERNAME",
273 help="Set the user (usually only possible when run as root)")
273 help="Set the user (usually only possible when run as root)")
274 parser.add_option(
274 parser.add_option(
275 '--group',
275 '--group',
276 dest='set_group',
276 dest='set_group',
277 metavar="GROUP",
277 metavar="GROUP",
278 help="Set the group (usually only possible when run as root)")
278 help="Set the group (usually only possible when run as root)")
279
279
280 parser.add_option(
280 parser.add_option(
281 '--stop-daemon',
281 '--stop-daemon',
282 dest='stop_daemon',
282 dest='stop_daemon',
283 action='store_true',
283 action='store_true',
284 help=('Stop a daemonized server (given a PID file, or default '
284 help=('Stop a daemonized server (given a PID file, or default '
285 'pyramid.pid file)'))
285 'pyramid.pid file)'))
286
286
287 _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
287 _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
288
288
289 _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
289 _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
290 _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
290 _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
291
291
292 possible_subcommands = ('start', 'stop', 'restart', 'status')
292 possible_subcommands = ('start', 'stop', 'restart', 'status')
293
293
294 def __init__(self, argv, quiet=False):
294 def __init__(self, argv, quiet=False):
295 self.options, self.args = self.parser.parse_args(argv[1:])
295 self.options, self.args = self.parser.parse_args(argv[1:])
296 if quiet:
296 if quiet:
297 self.options.verbose = 0
297 self.options.verbose = 0
298
298
299 def out(self, msg): # pragma: no cover
299 def out(self, msg): # pragma: no cover
300 if self.options.verbose > 0:
300 if self.options.verbose > 0:
301 print(msg)
301 print(msg)
302
302
303 def get_options(self):
303 def get_options(self):
304 if (len(self.args) > 1
304 if (len(self.args) > 1
305 and self.args[1] in self.possible_subcommands):
305 and self.args[1] in self.possible_subcommands):
306 restvars = self.args[2:]
306 restvars = self.args[2:]
307 else:
307 else:
308 restvars = self.args[1:]
308 restvars = self.args[1:]
309
309
310 return parse_vars(restvars)
310 return parse_vars(restvars)
311
311
312 def run(self): # pragma: no cover
312 def run(self): # pragma: no cover
313 if self.options.stop_daemon:
313 if self.options.stop_daemon:
314 return self.stop_daemon()
314 return self.stop_daemon()
315
315
316 if not hasattr(self.options, 'set_user'):
316 if not hasattr(self.options, 'set_user'):
317 # Windows case:
317 # Windows case:
318 self.options.set_user = self.options.set_group = None
318 self.options.set_user = self.options.set_group = None
319
319
320 # @@: Is this the right stage to set the user at?
320 # @@: Is this the right stage to set the user at?
321 self.change_user_group(
321 self.change_user_group(
322 self.options.set_user, self.options.set_group)
322 self.options.set_user, self.options.set_group)
323
323
324 if not self.args:
324 if not self.args:
325 self.out('Please provide configuration file as first argument, '
325 self.out('Please provide configuration file as first argument, '
326 'most likely it should be production.ini')
326 'most likely it should be production.ini')
327 return 2
327 return 2
328 app_spec = self.args[0]
328 app_spec = self.args[0]
329
329
330 if (len(self.args) > 1
330 if (len(self.args) > 1
331 and self.args[1] in self.possible_subcommands):
331 and self.args[1] in self.possible_subcommands):
332 cmd = self.args[1]
332 cmd = self.args[1]
333 else:
333 else:
334 cmd = None
334 cmd = None
335
335
336 if self.options.reload:
336 if self.options.reload:
337 if os.environ.get(self._reloader_environ_key):
337 if os.environ.get(self._reloader_environ_key):
338 if self.options.verbose > 1:
338 if self.options.verbose > 1:
339 self.out('Running reloading file monitor')
339 self.out('Running reloading file monitor')
340
340
341 install_reloader(int(self.options.reload_interval),
341 install_reloader(int(self.options.reload_interval),
342 [app_spec] + generate_extra_file_list())
342 [app_spec] + generate_extra_file_list())
343 # if self.requires_config_file:
343 # if self.requires_config_file:
344 # watch_file(self.args[0])
344 # watch_file(self.args[0])
345 else:
345 else:
346 return self.restart_with_reloader()
346 return self.restart_with_reloader()
347
347
348 if cmd not in (None, 'start', 'stop', 'restart', 'status'):
348 if cmd not in (None, 'start', 'stop', 'restart', 'status'):
349 self.out(
349 self.out(
350 'Error: must give start|stop|restart (not %s)' % cmd)
350 'Error: must give start|stop|restart (not %s)' % cmd)
351 return 2
351 return 2
352
352
353 if cmd == 'status' or self.options.show_status:
353 if cmd == 'status' or self.options.show_status:
354 return self.show_status()
354 return self.show_status()
355
355
356 if cmd == 'restart' or cmd == 'stop':
356 if cmd == 'restart' or cmd == 'stop':
357 result = self.stop_daemon()
357 result = self.stop_daemon()
358 if result:
358 if result:
359 if cmd == 'restart':
359 if cmd == 'restart':
360 self.out("Could not stop daemon; aborting")
360 self.out("Could not stop daemon; aborting")
361 else:
361 else:
362 self.out("Could not stop daemon")
362 self.out("Could not stop daemon")
363 return result
363 return result
364 if cmd == 'stop':
364 if cmd == 'stop':
365 return result
365 return result
366 self.options.daemon = True
366 self.options.daemon = True
367
367
368 if cmd == 'start':
368 if cmd == 'start':
369 self.options.daemon = True
369 self.options.daemon = True
370
370
371 app_name = self.options.app_name
371 app_name = self.options.app_name
372
372
373 vars = self.get_options()
373 vars = self.get_options()
374
374
375 if self.options.vcs_server:
375 if self.options.vcs_server:
376 vars['vcs.start_server'] = 'true'
376 vars['vcs.start_server'] = 'true'
377
377
378 if self.options.running_file:
378 if self.options.running_file:
379 global SERVER_RUNNING_FILE
379 global SERVER_RUNNING_FILE
380 SERVER_RUNNING_FILE = self.options.running_file
380 SERVER_RUNNING_FILE = self.options.running_file
381
381
382 if not self._scheme_re.search(app_spec):
382 if not self._scheme_re.search(app_spec):
383 app_spec = 'config:' + app_spec
383 app_spec = 'config:' + app_spec
384 server_name = self.options.server_name
384 server_name = self.options.server_name
385 if self.options.server:
385 if self.options.server:
386 server_spec = 'egg:pyramid'
386 server_spec = 'egg:pyramid'
387 assert server_name is None
387 assert server_name is None
388 server_name = self.options.server
388 server_name = self.options.server
389 else:
389 else:
390 server_spec = app_spec
390 server_spec = app_spec
391 base = os.getcwd()
391 base = os.getcwd()
392
392
393 if getattr(self.options, 'daemon', False):
393 if getattr(self.options, 'daemon', False):
394 if not self.options.pid_file:
394 if not self.options.pid_file:
395 self.options.pid_file = 'pyramid.pid'
395 self.options.pid_file = 'pyramid.pid'
396 if not self.options.log_file:
396 if not self.options.log_file:
397 self.options.log_file = 'pyramid.log'
397 self.options.log_file = 'pyramid.log'
398
398
399 # Ensure the log file is writeable
399 # Ensure the log file is writeable
400 if self.options.log_file:
400 if self.options.log_file:
401 try:
401 try:
402 writeable_log_file = open(self.options.log_file, 'a')
402 writeable_log_file = open(self.options.log_file, 'a')
403 except IOError as ioe:
403 except IOError as ioe:
404 msg = 'Error: Unable to write to log file: %s' % ioe
404 msg = 'Error: Unable to write to log file: %s' % ioe
405 raise ValueError(msg)
405 raise ValueError(msg)
406 writeable_log_file.close()
406 writeable_log_file.close()
407
407
408 # Ensure the pid file is writeable
408 # Ensure the pid file is writeable
409 if self.options.pid_file:
409 if self.options.pid_file:
410 try:
410 try:
411 writeable_pid_file = open(self.options.pid_file, 'a')
411 writeable_pid_file = open(self.options.pid_file, 'a')
412 except IOError as ioe:
412 except IOError as ioe:
413 msg = 'Error: Unable to write to pid file: %s' % ioe
413 msg = 'Error: Unable to write to pid file: %s' % ioe
414 raise ValueError(msg)
414 raise ValueError(msg)
415 writeable_pid_file.close()
415 writeable_pid_file.close()
416
416
417
417
418 if getattr(self.options, 'daemon', False):
418 if getattr(self.options, 'daemon', False):
419 try:
419 try:
420 self.daemonize()
420 self.daemonize()
421 except DaemonizeException as ex:
421 except DaemonizeException as ex:
422 if self.options.verbose > 0:
422 if self.options.verbose > 0:
423 self.out(str(ex))
423 self.out(str(ex))
424 return 2
424 return 2
425
425
426 if (self.options.monitor_restart
426 if (self.options.monitor_restart
427 and not os.environ.get(self._monitor_environ_key)):
427 and not os.environ.get(self._monitor_environ_key)):
428 return self.restart_with_monitor()
428 return self.restart_with_monitor()
429
429
430 if self.options.pid_file:
430 if self.options.pid_file:
431 self.record_pid(self.options.pid_file)
431 self.record_pid(self.options.pid_file)
432
432
433 if self.options.log_file:
433 if self.options.log_file:
434 stdout_log = LazyWriter(self.options.log_file, 'a')
434 stdout_log = LazyWriter(self.options.log_file, 'a')
435 sys.stdout = stdout_log
435 sys.stdout = stdout_log
436 sys.stderr = stdout_log
436 sys.stderr = stdout_log
437 logging.basicConfig(stream=stdout_log)
437 logging.basicConfig(stream=stdout_log)
438
438
439 log_fn = app_spec
439 log_fn = app_spec
440 if log_fn.startswith('config:'):
440 if log_fn.startswith('config:'):
441 log_fn = app_spec[len('config:'):]
441 log_fn = app_spec[len('config:'):]
442 elif log_fn.startswith('egg:'):
442 elif log_fn.startswith('egg:'):
443 log_fn = None
443 log_fn = None
444 if log_fn:
444 if log_fn:
445 log_fn = os.path.join(base, log_fn)
445 log_fn = os.path.join(base, log_fn)
446 setup_logging(log_fn)
446 setup_logging(log_fn)
447 set_rhodecode_is_test(log_fn)
447 set_rhodecode_is_test(log_fn)
448
448
449 server = self.loadserver(server_spec, name=server_name,
449 server = self.loadserver(server_spec, name=server_name,
450 relative_to=base, global_conf=vars)
450 relative_to=base, global_conf=vars)
451 # starting hooks
451 # starting hooks
452 app = self.loadapp(app_spec, name=app_name, relative_to=base,
452 app = self.loadapp(app_spec, name=app_name, relative_to=base,
453 global_conf=vars)
453 global_conf=vars)
454
454
455 if self.options.verbose > 0:
455 if self.options.verbose > 0:
456 if hasattr(os, 'getpid'):
456 if hasattr(os, 'getpid'):
457 msg = 'Starting %s in PID %i.' % (__name__, os.getpid())
457 msg = 'Starting %s in PID %i.' % (__name__, os.getpid())
458 else:
458 else:
459 msg = 'Starting %s.' % (__name__,)
459 msg = 'Starting %s.' % (__name__,)
460 self.out(msg)
460 self.out(msg)
461 if SERVER_RUNNING_FILE:
461 if SERVER_RUNNING_FILE:
462 self.out('PID file written as %s' % (SERVER_RUNNING_FILE, ))
462 self.out('PID file written as %s' % (SERVER_RUNNING_FILE, ))
463 elif not self.options.pid_file:
463 elif not self.options.pid_file:
464 self.out('No PID file written by default.')
464 self.out('No PID file written by default.')
465
465
466 try:
466 try:
467 when_ready(server)
467 when_ready(server)
468 server(app)
468 server(app)
469 except (SystemExit, KeyboardInterrupt) as e:
469 except (SystemExit, KeyboardInterrupt) as e:
470 if self.options.verbose > 1:
470 if self.options.verbose > 1:
471 raise
471 raise
472 if str(e):
472 if str(e):
473 msg = ' ' + str(e)
473 msg = ' ' + str(e)
474 else:
474 else:
475 msg = ''
475 msg = ''
476 self.out('Exiting%s (-v to see traceback)' % msg)
476 self.out('Exiting%s (-v to see traceback)' % msg)
477
477
478
479 def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover
478 def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover
480 return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
479 return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
481
480
482 def loadserver(self, server_spec, name, relative_to, **kw): # pragma:no cover
481 def loadserver(self, server_spec, name, relative_to, **kw): # pragma: no cover
483 return loadserver(
482 return loadserver(
484 server_spec, name=name, relative_to=relative_to, **kw)
483 server_spec, name=name, relative_to=relative_to, **kw)
485
484
486 def quote_first_command_arg(self, arg): # pragma: no cover
485 def quote_first_command_arg(self, arg): # pragma: no cover
487 """
486 """
488 There's a bug in Windows when running an executable that's
487 There's a bug in Windows when running an executable that's
489 located inside a path with a space in it. This method handles
488 located inside a path with a space in it. This method handles
490 that case, or on non-Windows systems or an executable with no
489 that case, or on non-Windows systems or an executable with no
491 spaces, it just leaves well enough alone.
490 spaces, it just leaves well enough alone.
492 """
491 """
493 if sys.platform != 'win32' or ' ' not in arg:
492 if sys.platform != 'win32' or ' ' not in arg:
494 # Problem does not apply:
493 # Problem does not apply:
495 return arg
494 return arg
496 try:
495 try:
497 import win32api
496 import win32api
498 except ImportError:
497 except ImportError:
499 raise ValueError(
498 raise ValueError(
500 "The executable %r contains a space, and in order to "
499 "The executable %r contains a space, and in order to "
501 "handle this issue you must have the win32api module "
500 "handle this issue you must have the win32api module "
502 "installed" % arg)
501 "installed" % arg)
503 arg = win32api.GetShortPathName(arg)
502 arg = win32api.GetShortPathName(arg)
504 return arg
503 return arg
505
504
506 def daemonize(self): # pragma: no cover
505 def daemonize(self): # pragma: no cover
507 pid = live_pidfile(self.options.pid_file)
506 pid = live_pidfile(self.options.pid_file)
508 if pid:
507 if pid:
509 raise DaemonizeException(
508 raise DaemonizeException(
510 "Daemon is already running (PID: %s from PID file %s)"
509 "Daemon is already running (PID: %s from PID file %s)"
511 % (pid, self.options.pid_file))
510 % (pid, self.options.pid_file))
512
511
513 if self.options.verbose > 0:
512 if self.options.verbose > 0:
514 self.out('Entering daemon mode')
513 self.out('Entering daemon mode')
515 pid = os.fork()
514 pid = os.fork()
516 if pid:
515 if pid:
517 # The forked process also has a handle on resources, so we
516 # The forked process also has a handle on resources, so we
518 # *don't* want proper termination of the process, we just
517 # *don't* want proper termination of the process, we just
519 # want to exit quick (which os._exit() does)
518 # want to exit quick (which os._exit() does)
520 os._exit(0)
519 os._exit(0)
521 # Make this the session leader
520 # Make this the session leader
522 os.setsid()
521 os.setsid()
523 # Fork again for good measure!
522 # Fork again for good measure!
524 pid = os.fork()
523 pid = os.fork()
525 if pid:
524 if pid:
526 os._exit(0)
525 os._exit(0)
527
526
528 # @@: Should we set the umask and cwd now?
527 # @@: Should we set the umask and cwd now?
529
528
530 import resource # Resource usage information.
529 import resource # Resource usage information.
531 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
530 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
532 if maxfd == resource.RLIM_INFINITY:
531 if maxfd == resource.RLIM_INFINITY:
533 maxfd = MAXFD
532 maxfd = MAXFD
534 # Iterate through and close all file descriptors.
533 # Iterate through and close all file descriptors.
535 for fd in range(0, maxfd):
534 for fd in range(0, maxfd):
536 try:
535 try:
537 os.close(fd)
536 os.close(fd)
538 except OSError: # ERROR, fd wasn't open to begin with (ignored)
537 except OSError: # ERROR, fd wasn't open to begin with (ignored)
539 pass
538 pass
540
539
541 if hasattr(os, "devnull"):
540 if hasattr(os, "devnull"):
542 REDIRECT_TO = os.devnull
541 REDIRECT_TO = os.devnull
543 else:
542 else:
544 REDIRECT_TO = "/dev/null"
543 REDIRECT_TO = "/dev/null"
545 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
544 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
546 # Duplicate standard input to standard output and standard error.
545 # Duplicate standard input to standard output and standard error.
547 os.dup2(0, 1) # standard output (1)
546 os.dup2(0, 1) # standard output (1)
548 os.dup2(0, 2) # standard error (2)
547 os.dup2(0, 2) # standard error (2)
549
548
550 def _remove_pid_file(self, written_pid, filename, verbosity):
549 def _remove_pid_file(self, written_pid, filename, verbosity):
551 current_pid = os.getpid()
550 current_pid = os.getpid()
552 if written_pid != current_pid:
551 if written_pid != current_pid:
553 # A forked process must be exiting, not the process that
552 # A forked process must be exiting, not the process that
554 # wrote the PID file
553 # wrote the PID file
555 return
554 return
556 if not os.path.exists(filename):
555 if not os.path.exists(filename):
557 return
556 return
558 with open(filename) as f:
557 with open(filename) as f:
559 content = f.read().strip()
558 content = f.read().strip()
560 try:
559 try:
561 pid_in_file = int(content)
560 pid_in_file = int(content)
562 except ValueError:
561 except ValueError:
563 pass
562 pass
564 else:
563 else:
565 if pid_in_file != current_pid:
564 if pid_in_file != current_pid:
566 msg = "PID file %s contains %s, not expected PID %s"
565 msg = "PID file %s contains %s, not expected PID %s"
567 self.out(msg % (filename, pid_in_file, current_pid))
566 self.out(msg % (filename, pid_in_file, current_pid))
568 return
567 return
569 if verbosity > 0:
568 if verbosity > 0:
570 self.out("Removing PID file %s" % filename)
569 self.out("Removing PID file %s" % filename)
571 try:
570 try:
572 os.unlink(filename)
571 os.unlink(filename)
573 return
572 return
574 except OSError as e:
573 except OSError as e:
575 # Record, but don't give traceback
574 # Record, but don't give traceback
576 self.out("Cannot remove PID file: (%s)" % e)
575 self.out("Cannot remove PID file: (%s)" % e)
577 # well, at least lets not leave the invalid PID around...
576 # well, at least lets not leave the invalid PID around...
578 try:
577 try:
579 with open(filename, 'w') as f:
578 with open(filename, 'w') as f:
580 f.write('')
579 f.write('')
581 except OSError as e:
580 except OSError as e:
582 self.out('Stale PID left in file: %s (%s)' % (filename, e))
581 self.out('Stale PID left in file: %s (%s)' % (filename, e))
583 else:
582 else:
584 self.out('Stale PID removed')
583 self.out('Stale PID removed')
585
584
586 def record_pid(self, pid_file):
585 def record_pid(self, pid_file):
587 pid = os.getpid()
586 pid = os.getpid()
588 if self.options.verbose > 1:
587 if self.options.verbose > 1:
589 self.out('Writing PID %s to %s' % (pid, pid_file))
588 self.out('Writing PID %s to %s' % (pid, pid_file))
590 with open(pid_file, 'w') as f:
589 with open(pid_file, 'w') as f:
591 f.write(str(pid))
590 f.write(str(pid))
592 atexit.register(self._remove_pid_file, pid, pid_file, self.options.verbose)
591 atexit.register(self._remove_pid_file, pid, pid_file, self.options.verbose)
593
592
594 def stop_daemon(self): # pragma: no cover
593 def stop_daemon(self): # pragma: no cover
595 pid_file = self.options.pid_file or 'pyramid.pid'
594 pid_file = self.options.pid_file or 'pyramid.pid'
596 if not os.path.exists(pid_file):
595 if not os.path.exists(pid_file):
597 self.out('No PID file exists in %s' % pid_file)
596 self.out('No PID file exists in %s' % pid_file)
598 return 1
597 return 1
599 pid = read_pidfile(pid_file)
598 pid = read_pidfile(pid_file)
600 if not pid:
599 if not pid:
601 self.out("Not a valid PID file in %s" % pid_file)
600 self.out("Not a valid PID file in %s" % pid_file)
602 return 1
601 return 1
603 pid = live_pidfile(pid_file)
602 pid = live_pidfile(pid_file)
604 if not pid:
603 if not pid:
605 self.out("PID in %s is not valid (deleting)" % pid_file)
604 self.out("PID in %s is not valid (deleting)" % pid_file)
606 try:
605 try:
607 os.unlink(pid_file)
606 os.unlink(pid_file)
608 except (OSError, IOError) as e:
607 except (OSError, IOError) as e:
609 self.out("Could not delete: %s" % e)
608 self.out("Could not delete: %s" % e)
610 return 2
609 return 2
611 return 1
610 return 1
612 for j in range(10):
611 for j in range(10):
613 if not live_pidfile(pid_file):
612 if not live_pidfile(pid_file):
614 break
613 break
615 import signal
614 import signal
616 kill(pid, signal.SIGTERM)
615 kill(pid, signal.SIGTERM)
617 time.sleep(1)
616 time.sleep(1)
618 else:
617 else:
619 self.out("failed to kill web process %s" % pid)
618 self.out("failed to kill web process %s" % pid)
620 return 3
619 return 3
621 if os.path.exists(pid_file):
620 if os.path.exists(pid_file):
622 os.unlink(pid_file)
621 os.unlink(pid_file)
623 return 0
622 return 0
624
623
625 def show_status(self): # pragma: no cover
624 def show_status(self): # pragma: no cover
626 pid_file = self.options.pid_file or 'pyramid.pid'
625 pid_file = self.options.pid_file or 'pyramid.pid'
627 if not os.path.exists(pid_file):
626 if not os.path.exists(pid_file):
628 self.out('No PID file %s' % pid_file)
627 self.out('No PID file %s' % pid_file)
629 return 1
628 return 1
630 pid = read_pidfile(pid_file)
629 pid = read_pidfile(pid_file)
631 if not pid:
630 if not pid:
632 self.out('No PID in file %s' % pid_file)
631 self.out('No PID in file %s' % pid_file)
633 return 1
632 return 1
634 pid = live_pidfile(pid_file)
633 pid = live_pidfile(pid_file)
635 if not pid:
634 if not pid:
636 self.out('PID %s in %s is not running' % (pid, pid_file))
635 self.out('PID %s in %s is not running' % (pid, pid_file))
637 return 1
636 return 1
638 self.out('Server running in PID %s' % pid)
637 self.out('Server running in PID %s' % pid)
639 return 0
638 return 0
640
639
641 def restart_with_reloader(self): # pragma: no cover
640 def restart_with_reloader(self): # pragma: no cover
642 self.restart_with_monitor(reloader=True)
641 self.restart_with_monitor(reloader=True)
643
642
644 def restart_with_monitor(self, reloader=False): # pragma: no cover
643 def restart_with_monitor(self, reloader=False): # pragma: no cover
645 if self.options.verbose > 0:
644 if self.options.verbose > 0:
646 if reloader:
645 if reloader:
647 self.out('Starting subprocess with file monitor')
646 self.out('Starting subprocess with file monitor')
648 else:
647 else:
649 self.out('Starting subprocess with monitor parent')
648 self.out('Starting subprocess with monitor parent')
650 while 1:
649 while 1:
651 args = [self.quote_first_command_arg(sys.executable)] + sys.argv
650 args = [self.quote_first_command_arg(sys.executable)] + sys.argv
652 new_environ = os.environ.copy()
651 new_environ = os.environ.copy()
653 if reloader:
652 if reloader:
654 new_environ[self._reloader_environ_key] = 'true'
653 new_environ[self._reloader_environ_key] = 'true'
655 else:
654 else:
656 new_environ[self._monitor_environ_key] = 'true'
655 new_environ[self._monitor_environ_key] = 'true'
657 proc = None
656 proc = None
658 try:
657 try:
659 try:
658 try:
660 _turn_sigterm_into_systemexit()
659 _turn_sigterm_into_systemexit()
661 proc = subprocess32.Popen(args, env=new_environ)
660 proc = subprocess32.Popen(args, env=new_environ)
662 exit_code = proc.wait()
661 exit_code = proc.wait()
663 proc = None
662 proc = None
664 except KeyboardInterrupt:
663 except KeyboardInterrupt:
665 self.out('^C caught in monitor process')
664 self.out('^C caught in monitor process')
666 if self.options.verbose > 1:
665 if self.options.verbose > 1:
667 raise
666 raise
668 return 1
667 return 1
669 finally:
668 finally:
670 if proc is not None:
669 if proc is not None:
671 import signal
670 import signal
672 try:
671 try:
673 kill(proc.pid, signal.SIGTERM)
672 kill(proc.pid, signal.SIGTERM)
674 except (OSError, IOError):
673 except (OSError, IOError):
675 pass
674 pass
676
675
677 if reloader:
676 if reloader:
678 # Reloader always exits with code 3; but if we are
677 # Reloader always exits with code 3; but if we are
679 # a monitor, any exit code will restart
678 # a monitor, any exit code will restart
680 if exit_code != 3:
679 if exit_code != 3:
681 return exit_code
680 return exit_code
682 if self.options.verbose > 0:
681 if self.options.verbose > 0:
683 self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20))
682 self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20))
684
683
685 def change_user_group(self, user, group): # pragma: no cover
684 def change_user_group(self, user, group): # pragma: no cover
686 if not user and not group:
685 if not user and not group:
687 return
686 return
688 import pwd
687 import pwd
689 import grp
688 import grp
690 uid = gid = None
689 uid = gid = None
691 if group:
690 if group:
692 try:
691 try:
693 gid = int(group)
692 gid = int(group)
694 group = grp.getgrgid(gid).gr_name
693 group = grp.getgrgid(gid).gr_name
695 except ValueError:
694 except ValueError:
696 try:
695 try:
697 entry = grp.getgrnam(group)
696 entry = grp.getgrnam(group)
698 except KeyError:
697 except KeyError:
699 raise ValueError(
698 raise ValueError(
700 "Bad group: %r; no such group exists" % group)
699 "Bad group: %r; no such group exists" % group)
701 gid = entry.gr_gid
700 gid = entry.gr_gid
702 try:
701 try:
703 uid = int(user)
702 uid = int(user)
704 user = pwd.getpwuid(uid).pw_name
703 user = pwd.getpwuid(uid).pw_name
705 except ValueError:
704 except ValueError:
706 try:
705 try:
707 entry = pwd.getpwnam(user)
706 entry = pwd.getpwnam(user)
708 except KeyError:
707 except KeyError:
709 raise ValueError(
708 raise ValueError(
710 "Bad username: %r; no such user exists" % user)
709 "Bad username: %r; no such user exists" % user)
711 if not gid:
710 if not gid:
712 gid = entry.pw_gid
711 gid = entry.pw_gid
713 uid = entry.pw_uid
712 uid = entry.pw_uid
714 if self.options.verbose > 0:
713 if self.options.verbose > 0:
715 self.out('Changing user to %s:%s (%s:%s)' % (
714 self.out('Changing user to %s:%s (%s:%s)' % (
716 user, group or '(unknown)', uid, gid))
715 user, group or '(unknown)', uid, gid))
717 if gid:
716 if gid:
718 os.setgid(gid)
717 os.setgid(gid)
719 if uid:
718 if uid:
720 os.setuid(uid)
719 os.setuid(uid)
721
720
722
721
723 class LazyWriter(object):
722 class LazyWriter(object):
724
723
725 """
724 """
726 File-like object that opens a file lazily when it is first written
725 File-like object that opens a file lazily when it is first written
727 to.
726 to.
728 """
727 """
729
728
730 def __init__(self, filename, mode='w'):
729 def __init__(self, filename, mode='w'):
731 self.filename = filename
730 self.filename = filename
732 self.fileobj = None
731 self.fileobj = None
733 self.lock = threading.Lock()
732 self.lock = threading.Lock()
734 self.mode = mode
733 self.mode = mode
735
734
736 def open(self):
735 def open(self):
737 if self.fileobj is None:
736 if self.fileobj is None:
738 with self.lock:
737 with self.lock:
739 self.fileobj = open(self.filename, self.mode)
738 self.fileobj = open(self.filename, self.mode)
740 return self.fileobj
739 return self.fileobj
741
740
742 def close(self):
741 def close(self):
743 fileobj = self.fileobj
742 fileobj = self.fileobj
744 if fileobj is not None:
743 if fileobj is not None:
745 fileobj.close()
744 fileobj.close()
746
745
747 def __del__(self):
746 def __del__(self):
748 self.close()
747 self.close()
749
748
750 def write(self, text):
749 def write(self, text):
751 fileobj = self.open()
750 fileobj = self.open()
752 fileobj.write(text)
751 fileobj.write(text)
753 fileobj.flush()
752 fileobj.flush()
754
753
755 def writelines(self, text):
754 def writelines(self, text):
756 fileobj = self.open()
755 fileobj = self.open()
757 fileobj.writelines(text)
756 fileobj.writelines(text)
758 fileobj.flush()
757 fileobj.flush()
759
758
760 def flush(self):
759 def flush(self):
761 self.open().flush()
760 self.open().flush()
762
761
763
762
764 def live_pidfile(pidfile): # pragma: no cover
763 def live_pidfile(pidfile): # pragma: no cover
765 """
764 """
766 (pidfile:str) -> int | None
765 (pidfile:str) -> int | None
767 Returns an int found in the named file, if there is one,
766 Returns an int found in the named file, if there is one,
768 and if there is a running process with that process id.
767 and if there is a running process with that process id.
769 Return None if no such process exists.
768 Return None if no such process exists.
770 """
769 """
771 pid = read_pidfile(pidfile)
770 pid = read_pidfile(pidfile)
772 if pid:
771 if pid:
773 try:
772 try:
774 kill(int(pid), 0)
773 kill(int(pid), 0)
775 return pid
774 return pid
776 except OSError as e:
775 except OSError as e:
777 if e.errno == errno.EPERM:
776 if e.errno == errno.EPERM:
778 return pid
777 return pid
779 return None
778 return None
780
779
781
780
782 def read_pidfile(filename):
781 def read_pidfile(filename):
783 if os.path.exists(filename):
782 if os.path.exists(filename):
784 try:
783 try:
785 with open(filename) as f:
784 with open(filename) as f:
786 content = f.read()
785 content = f.read()
787 return int(content.strip())
786 return int(content.strip())
788 except (ValueError, IOError):
787 except (ValueError, IOError):
789 return None
788 return None
790 else:
789 else:
791 return None
790 return None
792
791
793
792
794 def ensure_port_cleanup(
793 def ensure_port_cleanup(
795 bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover
794 bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover
796 """
795 """
797 This makes sure any open ports are closed.
796 This makes sure any open ports are closed.
798
797
799 Does this by connecting to them until they give connection
798 Does this by connecting to them until they give connection
800 refused. Servers should call like::
799 refused. Servers should call like::
801
800
802 ensure_port_cleanup([80, 443])
801 ensure_port_cleanup([80, 443])
803 """
802 """
804 atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
803 atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
805 sleeptime=sleeptime)
804 sleeptime=sleeptime)
806
805
807
806
808 def _cleanup_ports(
807 def _cleanup_ports(
809 bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover
808 bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover
810 # Wait for the server to bind to the port.
809 # Wait for the server to bind to the port.
811 import socket
810 import socket
812 import errno
811 import errno
813 for bound_address in bound_addresses:
812 for bound_address in bound_addresses:
814 for attempt in range(maxtries):
813 for attempt in range(maxtries):
815 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
814 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
816 try:
815 try:
817 sock.connect(bound_address)
816 sock.connect(bound_address)
818 except socket.error as e:
817 except socket.error as e:
819 if e.args[0] != errno.ECONNREFUSED:
818 if e.args[0] != errno.ECONNREFUSED:
820 raise
819 raise
821 break
820 break
822 else:
821 else:
823 time.sleep(sleeptime)
822 time.sleep(sleeptime)
824 else:
823 else:
825 raise SystemExit('Timeout waiting for port.')
824 raise SystemExit('Timeout waiting for port.')
826 sock.close()
825 sock.close()
827
826
828
827
829 def _turn_sigterm_into_systemexit(): # pragma: no cover
828 def _turn_sigterm_into_systemexit(): # pragma: no cover
830 """
829 """
831 Attempts to turn a SIGTERM exception into a SystemExit exception.
830 Attempts to turn a SIGTERM exception into a SystemExit exception.
832 """
831 """
833 try:
832 try:
834 import signal
833 import signal
835 except ImportError:
834 except ImportError:
836 return
835 return
837 def handle_term(signo, frame):
836 def handle_term(signo, frame):
838 raise SystemExit
837 raise SystemExit
839 signal.signal(signal.SIGTERM, handle_term)
838 signal.signal(signal.SIGTERM, handle_term)
840
839
841
840
842 def install_reloader(poll_interval=1, extra_files=None): # pragma: no cover
841 def install_reloader(poll_interval=1, extra_files=None): # pragma: no cover
843 """
842 """
844 Install the reloading monitor.
843 Install the reloading monitor.
845
844
846 On some platforms server threads may not terminate when the main
845 On some platforms server threads may not terminate when the main
847 thread does, causing ports to remain open/locked. The
846 thread does, causing ports to remain open/locked. The
848 ``raise_keyboard_interrupt`` option creates a unignorable signal
847 ``raise_keyboard_interrupt`` option creates a unignorable signal
849 which causes the whole application to shut-down (rudely).
848 which causes the whole application to shut-down (rudely).
850 """
849 """
851 mon = Monitor(poll_interval=poll_interval)
850 mon = Monitor(poll_interval=poll_interval)
852 if extra_files is None:
851 if extra_files is None:
853 extra_files = []
852 extra_files = []
854 mon.extra_files.extend(extra_files)
853 mon.extra_files.extend(extra_files)
855 t = threading.Thread(target=mon.periodic_reload)
854 t = threading.Thread(target=mon.periodic_reload)
856 t.setDaemon(True)
855 t.setDaemon(True)
857 t.start()
856 t.start()
858
857
859
858
860 class classinstancemethod(object):
859 class classinstancemethod(object):
861 """
860 """
862 Acts like a class method when called from a class, like an
861 Acts like a class method when called from a class, like an
863 instance method when called by an instance. The method should
862 instance method when called by an instance. The method should
864 take two arguments, 'self' and 'cls'; one of these will be None
863 take two arguments, 'self' and 'cls'; one of these will be None
865 depending on how the method was called.
864 depending on how the method was called.
866 """
865 """
867
866
868 def __init__(self, func):
867 def __init__(self, func):
869 self.func = func
868 self.func = func
870 self.__doc__ = func.__doc__
869 self.__doc__ = func.__doc__
871
870
872 def __get__(self, obj, type=None):
871 def __get__(self, obj, type=None):
873 return _methodwrapper(self.func, obj=obj, type=type)
872 return _methodwrapper(self.func, obj=obj, type=type)
874
873
875
874
876 class _methodwrapper(object):
875 class _methodwrapper(object):
877
876
878 def __init__(self, func, obj, type):
877 def __init__(self, func, obj, type):
879 self.func = func
878 self.func = func
880 self.obj = obj
879 self.obj = obj
881 self.type = type
880 self.type = type
882
881
883 def __call__(self, *args, **kw):
882 def __call__(self, *args, **kw):
884 assert not 'self' in kw and not 'cls' in kw, (
883 assert not 'self' in kw and not 'cls' in kw, (
885 "You cannot use 'self' or 'cls' arguments to a "
884 "You cannot use 'self' or 'cls' arguments to a "
886 "classinstancemethod")
885 "classinstancemethod")
887 return self.func(*((self.obj, self.type) + args), **kw)
886 return self.func(*((self.obj, self.type) + args), **kw)
888
887
889
888
890 class Monitor(object): # pragma: no cover
889 class Monitor(object): # pragma: no cover
891 """
890 """
892 A file monitor and server restarter.
891 A file monitor and server restarter.
893
892
894 Use this like:
893 Use this like:
895
894
896 ..code-block:: Python
895 ..code-block:: Python
897
896
898 install_reloader()
897 install_reloader()
899
898
900 Then make sure your server is installed with a shell script like::
899 Then make sure your server is installed with a shell script like::
901
900
902 err=3
901 err=3
903 while test "$err" -eq 3 ; do
902 while test "$err" -eq 3 ; do
904 python server.py
903 python server.py
905 err="$?"
904 err="$?"
906 done
905 done
907
906
908 or is run from this .bat file (if you use Windows)::
907 or is run from this .bat file (if you use Windows)::
909
908
910 @echo off
909 @echo off
911 :repeat
910 :repeat
912 python server.py
911 python server.py
913 if %errorlevel% == 3 goto repeat
912 if %errorlevel% == 3 goto repeat
914
913
915 or run a monitoring process in Python (``pserve --reload`` does
914 or run a monitoring process in Python (``pserve --reload`` does
916 this).
915 this).
917
916
918 Use the ``watch_file(filename)`` function to cause a reload/restart for
917 Use the ``watch_file(filename)`` function to cause a reload/restart for
919 other non-Python files (e.g., configuration files). If you have
918 other non-Python files (e.g., configuration files). If you have
920 a dynamic set of files that grows over time you can use something like::
919 a dynamic set of files that grows over time you can use something like::
921
920
922 def watch_config_files():
921 def watch_config_files():
923 return CONFIG_FILE_CACHE.keys()
922 return CONFIG_FILE_CACHE.keys()
924 add_file_callback(watch_config_files)
923 add_file_callback(watch_config_files)
925
924
926 Then every time the reloader polls files it will call
925 Then every time the reloader polls files it will call
927 ``watch_config_files`` and check all the filenames it returns.
926 ``watch_config_files`` and check all the filenames it returns.
928 """
927 """
929 instances = []
928 instances = []
930 global_extra_files = []
929 global_extra_files = []
931 global_file_callbacks = []
930 global_file_callbacks = []
932
931
933 def __init__(self, poll_interval):
932 def __init__(self, poll_interval):
934 self.module_mtimes = {}
933 self.module_mtimes = {}
935 self.keep_running = True
934 self.keep_running = True
936 self.poll_interval = poll_interval
935 self.poll_interval = poll_interval
937 self.extra_files = list(self.global_extra_files)
936 self.extra_files = list(self.global_extra_files)
938 self.instances.append(self)
937 self.instances.append(self)
939 self.file_callbacks = list(self.global_file_callbacks)
938 self.file_callbacks = list(self.global_file_callbacks)
940
939
941 def _exit(self):
940 def _exit(self):
942 # use os._exit() here and not sys.exit() since within a
941 # use os._exit() here and not sys.exit() since within a
943 # thread sys.exit() just closes the given thread and
942 # thread sys.exit() just closes the given thread and
944 # won't kill the process; note os._exit does not call
943 # won't kill the process; note os._exit does not call
945 # any atexit callbacks, nor does it do finally blocks,
944 # any atexit callbacks, nor does it do finally blocks,
946 # flush open files, etc. In otherwords, it is rude.
945 # flush open files, etc. In otherwords, it is rude.
947 os._exit(3)
946 os._exit(3)
948
947
949 def periodic_reload(self):
948 def periodic_reload(self):
950 while True:
949 while True:
951 if not self.check_reload():
950 if not self.check_reload():
952 self._exit()
951 self._exit()
953 break
952 break
954 time.sleep(self.poll_interval)
953 time.sleep(self.poll_interval)
955
954
956 def check_reload(self):
955 def check_reload(self):
957 filenames = list(self.extra_files)
956 filenames = list(self.extra_files)
958 for file_callback in self.file_callbacks:
957 for file_callback in self.file_callbacks:
959 try:
958 try:
960 filenames.extend(file_callback())
959 filenames.extend(file_callback())
961 except:
960 except:
962 print(
961 print(
963 "Error calling reloader callback %r:" % file_callback)
962 "Error calling reloader callback %r:" % file_callback)
964 traceback.print_exc()
963 traceback.print_exc()
965 for module in list(sys.modules.values()):
964 for module in list(sys.modules.values()):
966 try:
965 try:
967 filename = module.__file__
966 filename = module.__file__
968 except (AttributeError, ImportError):
967 except (AttributeError, ImportError):
969 continue
968 continue
970 if filename is not None:
969 if filename is not None:
971 filenames.append(filename)
970 filenames.append(filename)
972
971
973 for filename in filenames:
972 for filename in filenames:
974 try:
973 try:
975 stat = os.stat(filename)
974 stat = os.stat(filename)
976 if stat:
975 if stat:
977 mtime = stat.st_mtime
976 mtime = stat.st_mtime
978 else:
977 else:
979 mtime = 0
978 mtime = 0
980 except (OSError, IOError):
979 except (OSError, IOError):
981 continue
980 continue
982 if filename.endswith('.pyc') and os.path.exists(filename[:-1]):
981 if filename.endswith('.pyc') and os.path.exists(filename[:-1]):
983 mtime = max(os.stat(filename[:-1]).st_mtime, mtime)
982 mtime = max(os.stat(filename[:-1]).st_mtime, mtime)
984 if not filename in self.module_mtimes:
983 if not filename in self.module_mtimes:
985 self.module_mtimes[filename] = mtime
984 self.module_mtimes[filename] = mtime
986 elif self.module_mtimes[filename] < mtime:
985 elif self.module_mtimes[filename] < mtime:
987 print("%s changed; reloading..." % filename)
986 print("%s changed; reloading..." % filename)
988 run_callback_for_pattern(filename)
987 run_callback_for_pattern(filename)
989 return False
988 return False
990 return True
989 return True
991
990
992 def watch_file(self, cls, filename):
991 def watch_file(self, cls, filename):
993 """Watch the named file for changes"""
992 """Watch the named file for changes"""
994 filename = os.path.abspath(filename)
993 filename = os.path.abspath(filename)
995 if self is None:
994 if self is None:
996 for instance in cls.instances:
995 for instance in cls.instances:
997 instance.watch_file(filename)
996 instance.watch_file(filename)
998 cls.global_extra_files.append(filename)
997 cls.global_extra_files.append(filename)
999 else:
998 else:
1000 self.extra_files.append(filename)
999 self.extra_files.append(filename)
1001
1000
1002 watch_file = classinstancemethod(watch_file)
1001 watch_file = classinstancemethod(watch_file)
1003
1002
1004 def add_file_callback(self, cls, callback):
1003 def add_file_callback(self, cls, callback):
1005 """Add a callback -- a function that takes no parameters -- that will
1004 """Add a callback -- a function that takes no parameters -- that will
1006 return a list of filenames to watch for changes."""
1005 return a list of filenames to watch for changes."""
1007 if self is None:
1006 if self is None:
1008 for instance in cls.instances:
1007 for instance in cls.instances:
1009 instance.add_file_callback(callback)
1008 instance.add_file_callback(callback)
1010 cls.global_file_callbacks.append(callback)
1009 cls.global_file_callbacks.append(callback)
1011 else:
1010 else:
1012 self.file_callbacks.append(callback)
1011 self.file_callbacks.append(callback)
1013
1012
1014 add_file_callback = classinstancemethod(add_file_callback)
1013 add_file_callback = classinstancemethod(add_file_callback)
1015
1014
1016 watch_file = Monitor.watch_file
1015 watch_file = Monitor.watch_file
1017 add_file_callback = Monitor.add_file_callback
1016 add_file_callback = Monitor.add_file_callback
1018
1017
1019
1018
1020 def main(argv=sys.argv, quiet=False):
1019 def main(argv=sys.argv, quiet=False):
1021 command = RcServerCommand(argv, quiet=quiet)
1020 command = RcServerCommand(argv, quiet=quiet)
1022 return command.run()
1021 return command.run()
1023
1022
1024 if __name__ == '__main__': # pragma: no cover
1023 if __name__ == '__main__': # pragma: no cover
1025 sys.exit(main() or 0)
1024 sys.exit(main() or 0)
General Comments 0
You need to be logged in to leave comments. Login now