##// END OF EJS Templates
Merge branch default into stable
marcink -
r168:40e6b177 merge stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,55 b''
1 |RCE| 4.1.0 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2016-06-XX
8
9 General
10 ^^^^^^^
11
12 - Migrated more views to Pyramid. Those now include login, social plugins, search
13 - Started Implementing Pyramid Events system in exchange to rcextensions callbacks
14 - JS routes assets are now generated in development mode automatically
15 - ini: Add fallback authentication plugin setting. In case only one
16 authentication backend is enabled users can now enable fallback auth if
17 they cannot log-in due to external servers being down
18 - Bumped Mercurial to 3.8.3 version
19 - Bumped RhodeCode Tools to 0.8.3 version
20
21 New Features
22 ^^^^^^^^^^^^
23
24 - search: add syntax highlighting, line numbers and line context to file
25 content search results
26 - Go To switcher now searches commit hashes as well
27 - Token based authentication is now in CE edition as well
28 - User groups: added autocomplete widget to be able to select members of
29 other group as part of current group.
30
31 Security
32 ^^^^^^^^
33
34 - Added new action loggers for actions like adding/revoking permissions.
35 - permissions: show origin of permissions in permissions summary. Allows users
36 to see where and how permissions are inherited
37
38 Performance
39 ^^^^^^^^^^^
40
41
42
43 Fixes
44 ^^^^^
45
46 - api: gracefully handle errors on repos that are damaged or missing
47 from filesystem.
48 - logging: log the original error when a merge failure occurs
49 - #3965 Cannot change the owner of a user's group by using the API
50 - database is now initialized inside pyramid context
51 - fixed wrong check on LDAP plugin about missing ldap server
52 - Bring back multi-threaded workers to gunicorn for backward compatibility with
53 previous RhodeCode versions
54 - Commit dates are now properly handled as UTC. This fixes some issues
55 with displaying age of commits No newline at end of file
@@ -0,0 +1,225 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22 import logging
23
24 from sqlalchemy.ext.hybrid import hybrid_property
25
26 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
28 from rhodecode.authentication.routes import AuthnPluginResourceBase
29 from rhodecode.lib.colander_utils import strip_whitespace
30 from rhodecode.lib.utils2 import str2bool, safe_unicode
31 from rhodecode.model.db import User
32 from rhodecode.translation import _
33
34
35 log = logging.getLogger(__name__)
36
37
38 def plugin_factory(plugin_id, *args, **kwds):
39 """
40 Factory function that is called during plugin discovery.
41 It returns the plugin instance.
42 """
43 plugin = RhodeCodeAuthPlugin(plugin_id)
44 return plugin
45
46
47 class HeadersAuthnResource(AuthnPluginResourceBase):
48 pass
49
50
51 class HeadersSettingsSchema(AuthnPluginSettingsSchemaBase):
52 header = colander.SchemaNode(
53 colander.String(),
54 default='REMOTE_USER',
55 description=_('Header to extract the user from'),
56 preparer=strip_whitespace,
57 title=_('Header'),
58 widget='string')
59 fallback_header = colander.SchemaNode(
60 colander.String(),
61 default='HTTP_X_FORWARDED_USER',
62 description=_('Header to extract the user from when main one fails'),
63 preparer=strip_whitespace,
64 title=_('Fallback header'),
65 widget='string')
66 clean_username = colander.SchemaNode(
67 colander.Boolean(),
68 default=True,
69 description=_('Perform cleaning of user, if passed user has @ in '
70 'username then first part before @ is taken. '
71 'If there\'s \\ in the username only the part after '
72 ' \\ is taken'),
73 missing=False,
74 title=_('Clean username'),
75 widget='bool')
76
77
78 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
79
80 def includeme(self, config):
81 config.add_authn_plugin(self)
82 config.add_authn_resource(self.get_id(), HeadersAuthnResource(self))
83 config.add_view(
84 'rhodecode.authentication.views.AuthnPluginViewBase',
85 attr='settings_get',
86 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
87 request_method='GET',
88 route_name='auth_home',
89 context=HeadersAuthnResource)
90 config.add_view(
91 'rhodecode.authentication.views.AuthnPluginViewBase',
92 attr='settings_post',
93 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
94 request_method='POST',
95 route_name='auth_home',
96 context=HeadersAuthnResource)
97
98 def get_display_name(self):
99 return _('Headers')
100
101 def get_settings_schema(self):
102 return HeadersSettingsSchema()
103
104 @hybrid_property
105 def name(self):
106 return 'headers'
107
108 @property
109 def is_headers_auth(self):
110 return True
111
112 def use_fake_password(self):
113 return True
114
115 def user_activation_state(self):
116 def_user_perms = User.get_default_user().AuthUser.permissions['global']
117 return 'hg.extern_activate.auto' in def_user_perms
118
119 def _clean_username(self, username):
120 # Removing realm and domain from username
121 username = username.split('@')[0]
122 username = username.rsplit('\\')[-1]
123 return username
124
125 def _get_username(self, environ, settings):
126 username = None
127 environ = environ or {}
128 if not environ:
129 log.debug('got empty environ: %s' % environ)
130
131 settings = settings or {}
132 if settings.get('header'):
133 header = settings.get('header')
134 username = environ.get(header)
135 log.debug('extracted %s:%s' % (header, username))
136
137 # fallback mode
138 if not username and settings.get('fallback_header'):
139 header = settings.get('fallback_header')
140 username = environ.get(header)
141 log.debug('extracted %s:%s' % (header, username))
142
143 if username and str2bool(settings.get('clean_username')):
144 log.debug('Received username `%s` from headers' % username)
145 username = self._clean_username(username)
146 log.debug('New cleanup user is:%s' % username)
147 return username
148
149 def get_user(self, username=None, **kwargs):
150 """
151 Helper method for user fetching in plugins, by default it's using
152 simple fetch by username, but this method can be custimized in plugins
153 eg. headers auth plugin to fetch user by environ params
154 :param username: username if given to fetch
155 :param kwargs: extra arguments needed for user fetching.
156 """
157 environ = kwargs.get('environ') or {}
158 settings = kwargs.get('settings') or {}
159 username = self._get_username(environ, settings)
160 # we got the username, so use default method now
161 return super(RhodeCodeAuthPlugin, self).get_user(username)
162
163 def auth(self, userobj, username, password, settings, **kwargs):
164 """
165 Get's the headers_auth username (or email). It tries to get username
166 from REMOTE_USER if this plugin is enabled, if that fails
167 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
168 is set. clean_username extracts the username from this data if it's
169 having @ in it.
170 Return None on failure. On success, return a dictionary of the form:
171
172 see: RhodeCodeAuthPluginBase.auth_func_attrs
173
174 :param userobj:
175 :param username:
176 :param password:
177 :param settings:
178 :param kwargs:
179 """
180 environ = kwargs.get('environ')
181 if not environ:
182 log.debug('Empty environ data skipping...')
183 return None
184
185 if not userobj:
186 userobj = self.get_user('', environ=environ, settings=settings)
187
188 # we don't care passed username/password for headers auth plugins.
189 # only way to log in is using environ
190 username = None
191 if userobj:
192 username = getattr(userobj, 'username')
193
194 if not username:
195 # we don't have any objects in DB user doesn't exist extract
196 # username from environ based on the settings
197 username = self._get_username(environ, settings)
198
199 # if cannot fetch username, it's a no-go for this plugin to proceed
200 if not username:
201 return None
202
203 # old attrs fetched from RhodeCode database
204 admin = getattr(userobj, 'admin', False)
205 active = getattr(userobj, 'active', True)
206 email = getattr(userobj, 'email', '')
207 firstname = getattr(userobj, 'firstname', '')
208 lastname = getattr(userobj, 'lastname', '')
209 extern_type = getattr(userobj, 'extern_type', '')
210
211 user_attrs = {
212 'username': username,
213 'firstname': safe_unicode(firstname or username),
214 'lastname': safe_unicode(lastname or ''),
215 'groups': [],
216 'email': email or '',
217 'admin': admin or False,
218 'active': active,
219 'active_from_extern': True,
220 'extern_name': username,
221 'extern_type': extern_type,
222 }
223
224 log.info('user `%s` authenticated correctly' % user_attrs['username'])
225 return user_attrs
@@ -0,0 +1,136 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 RhodeCode authentication token plugin for built in internal auth
23 """
24
25 import logging
26
27 from sqlalchemy.ext.hybrid import hybrid_property
28
29 from rhodecode.translation import _
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, VCS_TYPE
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.model.db import User, UserApiKeys
33
34
35 log = logging.getLogger(__name__)
36
37
38 def plugin_factory(plugin_id, *args, **kwds):
39 plugin = RhodeCodeAuthPlugin(plugin_id)
40 return plugin
41
42
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
44 pass
45
46
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
48 """
49 Enables usage of authentication tokens for vcs operations.
50 """
51
52 def includeme(self, config):
53 config.add_authn_plugin(self)
54 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
55 config.add_view(
56 'rhodecode.authentication.views.AuthnPluginViewBase',
57 attr='settings_get',
58 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
59 request_method='GET',
60 route_name='auth_home',
61 context=RhodecodeAuthnResource)
62 config.add_view(
63 'rhodecode.authentication.views.AuthnPluginViewBase',
64 attr='settings_post',
65 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
66 request_method='POST',
67 route_name='auth_home',
68 context=RhodecodeAuthnResource)
69
70 def get_display_name(self):
71 return _('Rhodecode Token Auth')
72
73 @hybrid_property
74 def name(self):
75 return "authtoken"
76
77 def user_activation_state(self):
78 def_user_perms = User.get_default_user().AuthUser.permissions['global']
79 return 'hg.register.auto_activate' in def_user_perms
80
81 def allows_authentication_from(
82 self, user, allows_non_existing_user=True,
83 allowed_auth_plugins=None, allowed_auth_sources=None):
84 """
85 Custom method for this auth that doesn't accept empty users. And also
86 allows rhodecode and authtoken extern_type to auth with this. But only
87 via vcs mode
88 """
89 # only this and rhodecode plugins can use this type
90 from rhodecode.authentication.plugins import auth_rhodecode
91 allowed_auth_plugins = [
92 self.name, auth_rhodecode.RhodeCodeAuthPlugin.name]
93 # only for vcs operations
94 allowed_auth_sources = [VCS_TYPE]
95
96 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
97 user, allows_non_existing_user=False,
98 allowed_auth_plugins=allowed_auth_plugins,
99 allowed_auth_sources=allowed_auth_sources)
100
101 def auth(self, userobj, username, password, settings, **kwargs):
102 if not userobj:
103 log.debug('userobj was:%s skipping' % (userobj, ))
104 return None
105
106 user_attrs = {
107 "username": userobj.username,
108 "firstname": userobj.firstname,
109 "lastname": userobj.lastname,
110 "groups": [],
111 "email": userobj.email,
112 "admin": userobj.admin,
113 "active": userobj.active,
114 "active_from_extern": userobj.active,
115 "extern_name": userobj.user_id,
116 "extern_type": userobj.extern_type,
117 }
118
119 log.debug('Authenticating user with args %s', user_attrs)
120 if userobj.active:
121 role = UserApiKeys.ROLE_VCS
122 active_tokens = [x.api_key for x in
123 User.extra_valid_auth_tokens(userobj, role=role)]
124 if userobj.username == username and password in active_tokens:
125 log.info(
126 'user `%s` successfully authenticated via %s',
127 user_attrs['username'], self.name)
128 return user_attrs
129 log.error(
130 'user `%s` failed to authenticate via %s, reason: bad or '
131 'inactive token.', username, self.name)
132 else:
133 log.warning(
134 'user `%s` failed to authenticate via %s, reason: account not '
135 'active.', username, self.name)
136 return None
@@ -0,0 +1,42 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 def generate_jsroutes_content(jsroutes):
22 statements = []
23 for url_name, url, fields in jsroutes:
24 statements.append(
25 "pyroutes.register('%s', '%s', %s);" % (url_name, url, fields))
26 return u'''
27 /******************************************************************************
28 * *
29 * DO NOT CHANGE THIS FILE MANUALLY *
30 * *
31 * *
32 * This file is automatically generated when the app starts up. *
33 * *
34 * To add a route here pass jsroute=True to the route definition in the app *
35 * *
36 ******************************************************************************/
37 function registerRCRoutes() {
38 // routes registration
39 %s
40 }
41 ''' % '\n '.join(statements)
42
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,6 +1,6 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 4.0.1
2 current_version = 4.1.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:rhodecode/VERSION]
5 [bumpversion:file:rhodecode/VERSION]
6
6
@@ -1,137 +1,138 b''
1 module.exports = function(grunt) {
1 module.exports = function(grunt) {
2 grunt.initConfig({
2 grunt.initConfig({
3
3
4 dirs: {
4 dirs: {
5 css: "rhodecode/public/css",
5 css: "rhodecode/public/css",
6 js: {
6 js: {
7 "src": "rhodecode/public/js/src",
7 "src": "rhodecode/public/js/src",
8 "dest": "rhodecode/public/js"
8 "dest": "rhodecode/public/js"
9 }
9 }
10 },
10 },
11
11
12 concat: {
12 concat: {
13 dist: {
13 dist: {
14 src: [
14 src: [
15 // Base libraries
15 // Base libraries
16 '<%= dirs.js.src %>/jquery-1.11.1.min.js',
16 '<%= dirs.js.src %>/jquery-1.11.1.min.js',
17 '<%= dirs.js.src %>/logging.js',
17 '<%= dirs.js.src %>/logging.js',
18 '<%= dirs.js.src %>/bootstrap.js',
18 '<%= dirs.js.src %>/bootstrap.js',
19 '<%= dirs.js.src %>/mousetrap.js',
19 '<%= dirs.js.src %>/mousetrap.js',
20 '<%= dirs.js.src %>/moment.js',
20 '<%= dirs.js.src %>/moment.js',
21 '<%= dirs.js.src %>/appenlight-client-0.4.1.min.js',
21 '<%= dirs.js.src %>/appenlight-client-0.4.1.min.js',
22
22
23 // Plugins
23 // Plugins
24 '<%= dirs.js.src %>/plugins/jquery.pjax.js',
24 '<%= dirs.js.src %>/plugins/jquery.pjax.js',
25 '<%= dirs.js.src %>/plugins/jquery.dataTables.js',
25 '<%= dirs.js.src %>/plugins/jquery.dataTables.js',
26 '<%= dirs.js.src %>/plugins/flavoured_checkbox.js',
26 '<%= dirs.js.src %>/plugins/flavoured_checkbox.js',
27 '<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js',
27 '<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js',
28 '<%= dirs.js.src %>/plugins/jquery.autocomplete.js',
28 '<%= dirs.js.src %>/plugins/jquery.autocomplete.js',
29 '<%= dirs.js.src %>/plugins/jquery.debounce.js',
29 '<%= dirs.js.src %>/plugins/jquery.debounce.js',
30 '<%= dirs.js.src %>/plugins/jquery.mark.js',
30 '<%= dirs.js.src %>/plugins/jquery.timeago.js',
31 '<%= dirs.js.src %>/plugins/jquery.timeago.js',
31 '<%= dirs.js.src %>/plugins/jquery.timeago-extension.js',
32 '<%= dirs.js.src %>/plugins/jquery.timeago-extension.js',
32
33
33 // Select2
34 // Select2
34 '<%= dirs.js.src %>/select2/select2.js',
35 '<%= dirs.js.src %>/select2/select2.js',
35
36
36 // Code-mirror
37 // Code-mirror
37 '<%= dirs.js.src %>/codemirror/codemirror.js',
38 '<%= dirs.js.src %>/codemirror/codemirror.js',
38 '<%= dirs.js.src %>/codemirror/codemirror_loadmode.js',
39 '<%= dirs.js.src %>/codemirror/codemirror_loadmode.js',
39 '<%= dirs.js.src %>/codemirror/codemirror_hint.js',
40 '<%= dirs.js.src %>/codemirror/codemirror_hint.js',
40 '<%= dirs.js.src %>/codemirror/codemirror_overlay.js',
41 '<%= dirs.js.src %>/codemirror/codemirror_overlay.js',
41 '<%= dirs.js.src %>/codemirror/codemirror_placeholder.js',
42 '<%= dirs.js.src %>/codemirror/codemirror_placeholder.js',
42 // TODO: mikhail: this is an exception. Since the code mirror modes
43 // TODO: mikhail: this is an exception. Since the code mirror modes
43 // are loaded "on the fly", we need to keep them in a public folder
44 // are loaded "on the fly", we need to keep them in a public folder
44 '<%= dirs.js.dest %>/mode/meta.js',
45 '<%= dirs.js.dest %>/mode/meta.js',
45 '<%= dirs.js.dest %>/mode/meta_ext.js',
46 '<%= dirs.js.dest %>/mode/meta_ext.js',
46 '<%= dirs.js.dest %>/rhodecode/i18n/select2/translations.js',
47 '<%= dirs.js.dest %>/rhodecode/i18n/select2/translations.js',
47
48
48 // Rhodecode utilities
49 // Rhodecode utilities
49 '<%= dirs.js.src %>/rhodecode/utils/array.js',
50 '<%= dirs.js.src %>/rhodecode/utils/array.js',
50 '<%= dirs.js.src %>/rhodecode/utils/string.js',
51 '<%= dirs.js.src %>/rhodecode/utils/string.js',
51 '<%= dirs.js.src %>/rhodecode/utils/pyroutes.js',
52 '<%= dirs.js.src %>/rhodecode/utils/pyroutes.js',
52 '<%= dirs.js.src %>/rhodecode/utils/ajax.js',
53 '<%= dirs.js.src %>/rhodecode/utils/ajax.js',
53 '<%= dirs.js.src %>/rhodecode/utils/autocomplete.js',
54 '<%= dirs.js.src %>/rhodecode/utils/autocomplete.js',
54 '<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js',
55 '<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js',
55 '<%= dirs.js.src %>/rhodecode/utils/ie.js',
56 '<%= dirs.js.src %>/rhodecode/utils/ie.js',
56 '<%= dirs.js.src %>/rhodecode/utils/os.js',
57 '<%= dirs.js.src %>/rhodecode/utils/os.js',
57
58
58 // Rhodecode widgets
59 // Rhodecode widgets
59 '<%= dirs.js.src %>/rhodecode/widgets/multiselect.js',
60 '<%= dirs.js.src %>/rhodecode/widgets/multiselect.js',
60
61
61 // Rhodecode components
62 // Rhodecode components
62 '<%= dirs.js.src %>/rhodecode/pyroutes.js',
63 '<%= dirs.js.src %>/rhodecode/init.js',
63 '<%= dirs.js.src %>/rhodecode/codemirror.js',
64 '<%= dirs.js.src %>/rhodecode/codemirror.js',
64 '<%= dirs.js.src %>/rhodecode/comments.js',
65 '<%= dirs.js.src %>/rhodecode/comments.js',
65 '<%= dirs.js.src %>/rhodecode/constants.js',
66 '<%= dirs.js.src %>/rhodecode/constants.js',
66 '<%= dirs.js.src %>/rhodecode/files.js',
67 '<%= dirs.js.src %>/rhodecode/files.js',
67 '<%= dirs.js.src %>/rhodecode/followers.js',
68 '<%= dirs.js.src %>/rhodecode/followers.js',
68 '<%= dirs.js.src %>/rhodecode/menus.js',
69 '<%= dirs.js.src %>/rhodecode/menus.js',
69 '<%= dirs.js.src %>/rhodecode/notifications.js',
70 '<%= dirs.js.src %>/rhodecode/notifications.js',
70 '<%= dirs.js.src %>/rhodecode/permissions.js',
71 '<%= dirs.js.src %>/rhodecode/permissions.js',
71 '<%= dirs.js.src %>/rhodecode/pjax.js',
72 '<%= dirs.js.src %>/rhodecode/pjax.js',
72 '<%= dirs.js.src %>/rhodecode/pullrequests.js',
73 '<%= dirs.js.src %>/rhodecode/pullrequests.js',
73 '<%= dirs.js.src %>/rhodecode/settings.js',
74 '<%= dirs.js.src %>/rhodecode/settings.js',
74 '<%= dirs.js.src %>/rhodecode/select2_widgets.js',
75 '<%= dirs.js.src %>/rhodecode/select2_widgets.js',
75 '<%= dirs.js.src %>/rhodecode/tooltips.js',
76 '<%= dirs.js.src %>/rhodecode/tooltips.js',
76 '<%= dirs.js.src %>/rhodecode/users.js',
77 '<%= dirs.js.src %>/rhodecode/users.js',
77 '<%= dirs.js.src %>/rhodecode/appenlight.js',
78 '<%= dirs.js.src %>/rhodecode/appenlight.js',
78
79
79 // Rhodecode main module
80 // Rhodecode main module
80 '<%= dirs.js.src %>/rhodecode.js'
81 '<%= dirs.js.src %>/rhodecode.js'
81 ],
82 ],
82 dest: '<%= dirs.js.dest %>/scripts.js',
83 dest: '<%= dirs.js.dest %>/scripts.js',
83 nonull: true
84 nonull: true
84 }
85 }
85 },
86 },
86
87
87 less: {
88 less: {
88 development: {
89 development: {
89 options: {
90 options: {
90 compress: false,
91 compress: false,
91 yuicompress: false,
92 yuicompress: false,
92 optimization: 0
93 optimization: 0
93 },
94 },
94 files: {
95 files: {
95 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
96 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
96 }
97 }
97 },
98 },
98 production: {
99 production: {
99 options: {
100 options: {
100 compress: true,
101 compress: true,
101 yuicompress: true,
102 yuicompress: true,
102 optimization: 2
103 optimization: 2
103 },
104 },
104 files: {
105 files: {
105 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
106 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
106 }
107 }
107 }
108 }
108 },
109 },
109
110
110 watch: {
111 watch: {
111 less: {
112 less: {
112 files: ["<%= dirs.css %>/*.less"],
113 files: ["<%= dirs.css %>/*.less"],
113 tasks: ["less:production"]
114 tasks: ["less:production"]
114 },
115 },
115 js: {
116 js: {
116 files: ["<%= dirs.js.src %>/**/*.js"],
117 files: ["<%= dirs.js.src %>/**/*.js"],
117 tasks: ["concat:dist"]
118 tasks: ["concat:dist"]
118 }
119 }
119 },
120 },
120
121
121 jshint: {
122 jshint: {
122 rhodecode: {
123 rhodecode: {
123 src: '<%= dirs.js.src %>/rhodecode/**/*.js',
124 src: '<%= dirs.js.src %>/rhodecode/**/*.js',
124 options: {
125 options: {
125 jshintrc: '.jshintrc'
126 jshintrc: '.jshintrc'
126 }
127 }
127 }
128 }
128 }
129 }
129 });
130 });
130
131
131 grunt.loadNpmTasks('grunt-contrib-less');
132 grunt.loadNpmTasks('grunt-contrib-less');
132 grunt.loadNpmTasks('grunt-contrib-concat');
133 grunt.loadNpmTasks('grunt-contrib-concat');
133 grunt.loadNpmTasks('grunt-contrib-watch');
134 grunt.loadNpmTasks('grunt-contrib-watch');
134 grunt.loadNpmTasks('grunt-contrib-jshint');
135 grunt.loadNpmTasks('grunt-contrib-jshint');
135
136
136 grunt.registerTask('default', ['less:production', 'concat:dist']);
137 grunt.registerTask('default', ['less:production', 'concat:dist']);
137 };
138 };
@@ -1,577 +1,608 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode Enterprise - configuration file #
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 # Built-in functions and variables #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
6 # #
7 ################################################################################
7 ################################################################################
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 pdebug = false
11 pdebug = false
12 ################################################################################
12 ################################################################################
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17 #email_to = admin@localhost
17 #email_to = admin@localhost
18 #error_email_from = paste_error@localhost
18 #error_email_from = paste_error@localhost
19 #app_email_from = rhodecode-noreply@localhost
19 #app_email_from = rhodecode-noreply@localhost
20 #error_message =
20 #error_message =
21 #email_prefix = [RhodeCode]
21 #email_prefix = [RhodeCode]
22
22
23 #smtp_server = mail.server.com
23 #smtp_server = mail.server.com
24 #smtp_username =
24 #smtp_username =
25 #smtp_password =
25 #smtp_password =
26 #smtp_port =
26 #smtp_port =
27 #smtp_use_tls = false
27 #smtp_use_tls = false
28 #smtp_use_ssl = true
28 #smtp_use_ssl = true
29 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
30 #smtp_auth =
30 #smtp_auth =
31
31
32 [server:main]
32 [server:main]
33 ## COMMON ##
33 ## COMMON ##
34 host = 127.0.0.1
34 host = 127.0.0.1
35 port = 5000
35 port = 5000
36
36
37 ##########################
37 ##################################
38 ## WAITRESS WSGI SERVER ##
38 ## WAITRESS WSGI SERVER ##
39 ##########################
39 ## Recommended for Development ##
40 ##################################
40 use = egg:waitress#main
41 use = egg:waitress#main
41 ## number of worker threads
42 ## number of worker threads
42 threads = 5
43 threads = 5
43 ## MAX BODY SIZE 100GB
44 ## MAX BODY SIZE 100GB
44 max_request_body_size = 107374182400
45 max_request_body_size = 107374182400
45 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## May not work on old windows systems.
47 ## May not work on old windows systems.
47 asyncore_use_poll = true
48 asyncore_use_poll = true
48
49
49
50
50 ##########################
51 ##########################
51 ## GUNICORN WSGI SERVER ##
52 ## GUNICORN WSGI SERVER ##
52 ##########################
53 ##########################
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 #use = egg:gunicorn#main
55 #use = egg:gunicorn#main
55 ## Sets the number of process workers. You must set `instance_id = *`
56 ## Sets the number of process workers. You must set `instance_id = *`
56 ## when this option is set to more than one worker, recommended
57 ## when this option is set to more than one worker, recommended
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 ## The `instance_id = *` must be set in the [app:main] section below
59 ## The `instance_id = *` must be set in the [app:main] section below
59 #workers = 1
60 #workers = 2
60 ## number of threads for each of the worker, must be set to 1 for gevent
61 ## number of threads for each of the worker, must be set to 1 for gevent
61 ## generally recommened to be at 1
62 ## generally recommened to be at 1
62 #threads = 1
63 #threads = 1
63 ## process name
64 ## process name
64 #proc_name = rhodecode
65 #proc_name = rhodecode
65 ## type of worker class, one of sync, gevent
66 ## type of worker class, one of sync, gevent
66 ## recommended for bigger setup is using of of other than sync one
67 ## recommended for bigger setup is using of of other than sync one
67 #worker_class = sync
68 #worker_class = sync
68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 ## The maximum number of simultaneous clients. Valid only for Gevent
69 #worker_connections = 10
70 #worker_connections = 10
70 ## max number of requests that worker will handle before being gracefully
71 ## max number of requests that worker will handle before being gracefully
71 ## restarted, could prevent memory leaks
72 ## restarted, could prevent memory leaks
72 #max_requests = 1000
73 #max_requests = 1000
73 #max_requests_jitter = 30
74 #max_requests_jitter = 30
74 ## ammount of time a worker can spend with handling a request before it
75 ## amount of time a worker can spend with handling a request before it
75 ## gets killed and restarted. Set to 6hrs
76 ## gets killed and restarted. Set to 6hrs
76 #timeout = 21600
77 #timeout = 21600
77
78
78
79
79 ## prefix middleware for RhodeCode, disables force_https flag.
80 ## prefix middleware for RhodeCode, disables force_https flag.
80 ## allows to set RhodeCode under a prefix in server.
81 ## allows to set RhodeCode under a prefix in server.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
83 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
84 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
85 #prefix = /<your-prefix>
85
86
86 [app:main]
87 [app:main]
87 use = egg:rhodecode-enterprise-ce
88 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
89 ## enable proxy prefix middleware, defined below
89 #filter-with = proxy-prefix
90 #filter-with = proxy-prefix
90
91
91 # During development the we want to have the debug toolbar enabled
92 # During development the we want to have the debug toolbar enabled
92 pyramid.includes =
93 pyramid.includes =
93 pyramid_debugtoolbar
94 pyramid_debugtoolbar
94 rhodecode.utils.debugtoolbar
95 rhodecode.utils.debugtoolbar
95 rhodecode.lib.middleware.request_wrapper
96 rhodecode.lib.middleware.request_wrapper
96
97
97 pyramid.reload_templates = true
98 pyramid.reload_templates = true
98
99
99 debugtoolbar.hosts = 0.0.0.0/0
100 debugtoolbar.hosts = 0.0.0.0/0
100 debugtoolbar.exclude_prefixes =
101 debugtoolbar.exclude_prefixes =
101 /css
102 /css
102 /fonts
103 /fonts
103 /images
104 /images
104 /js
105 /js
105
106
106 ## RHODECODE PLUGINS ##
107 ## RHODECODE PLUGINS ##
107 rhodecode.includes =
108 rhodecode.includes =
108 rhodecode.api
109 rhodecode.api
109
110
110
111
111 # api prefix url
112 # api prefix url
112 rhodecode.api.url = /_admin/api
113 rhodecode.api.url = /_admin/api
113
114
114
115
115 ## END RHODECODE PLUGINS ##
116 ## END RHODECODE PLUGINS ##
116
117
117 full_stack = true
118 full_stack = true
118
119
119 ## Serve static files via RhodeCode, disable to serve them via HTTP server
120 ## Serve static files via RhodeCode, disable to serve them via HTTP server
120 static_files = true
121 static_files = true
121
122
122 ## Optional Languages
123 ## Optional Languages
123 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
124 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
124 lang = en
125 lang = en
125
126
126 ## perform a full repository scan on each server start, this should be
127 ## perform a full repository scan on each server start, this should be
127 ## set to false after first startup, to allow faster server restarts.
128 ## set to false after first startup, to allow faster server restarts.
128 startup.import_repos = false
129 startup.import_repos = false
129
130
130 ## Uncomment and set this path to use archive download cache.
131 ## Uncomment and set this path to use archive download cache.
131 ## Once enabled, generated archives will be cached at this location
132 ## Once enabled, generated archives will be cached at this location
132 ## and served from the cache during subsequent requests for the same archive of
133 ## and served from the cache during subsequent requests for the same archive of
133 ## the repository.
134 ## the repository.
134 #archive_cache_dir = /tmp/tarballcache
135 #archive_cache_dir = /tmp/tarballcache
135
136
136 ## change this to unique ID for security
137 ## change this to unique ID for security
137 app_instance_uuid = rc-production
138 app_instance_uuid = rc-production
138
139
139 ## cut off limit for large diffs (size in bytes)
140 ## cut off limit for large diffs (size in bytes)
140 cut_off_limit_diff = 1024000
141 cut_off_limit_diff = 1024000
141 cut_off_limit_file = 256000
142 cut_off_limit_file = 256000
142
143
143 ## use cache version of scm repo everywhere
144 ## use cache version of scm repo everywhere
144 vcs_full_cache = true
145 vcs_full_cache = true
145
146
146 ## force https in RhodeCode, fixes https redirects, assumes it's always https
147 ## force https in RhodeCode, fixes https redirects, assumes it's always https
147 ## Normally this is controlled by proper http flags sent from http server
148 ## Normally this is controlled by proper http flags sent from http server
148 force_https = false
149 force_https = false
149
150
150 ## use Strict-Transport-Security headers
151 ## use Strict-Transport-Security headers
151 use_htsts = false
152 use_htsts = false
152
153
153 ## number of commits stats will parse on each iteration
154 ## number of commits stats will parse on each iteration
154 commit_parse_limit = 25
155 commit_parse_limit = 25
155
156
156 ## git rev filter option, --all is the default filter, if you need to
157 ## git rev filter option, --all is the default filter, if you need to
157 ## hide all refs in changelog switch this to --branches --tags
158 ## hide all refs in changelog switch this to --branches --tags
158 git_rev_filter = --branches --tags
159 git_rev_filter = --branches --tags
159
160
160 # Set to true if your repos are exposed using the dumb protocol
161 # Set to true if your repos are exposed using the dumb protocol
161 git_update_server_info = false
162 git_update_server_info = false
162
163
163 ## RSS/ATOM feed options
164 ## RSS/ATOM feed options
164 rss_cut_off_limit = 256000
165 rss_cut_off_limit = 256000
165 rss_items_per_page = 10
166 rss_items_per_page = 10
166 rss_include_diff = false
167 rss_include_diff = false
167
168
168 ## gist URL alias, used to create nicer urls for gist. This should be an
169 ## gist URL alias, used to create nicer urls for gist. This should be an
169 ## url that does rewrites to _admin/gists/<gistid>.
170 ## url that does rewrites to _admin/gists/<gistid>.
170 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
171 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
171 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
172 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
172 gist_alias_url =
173 gist_alias_url =
173
174
174 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
175 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
175 ## used for access.
176 ## used for access.
176 ## Adding ?auth_token = <token> to the url authenticates this request as if it
177 ## Adding ?auth_token = <token> to the url authenticates this request as if it
177 ## came from the the logged in user who own this authentication token.
178 ## came from the the logged in user who own this authentication token.
178 ##
179 ##
179 ## Syntax is <ControllerClass>:<function_pattern>.
180 ## Syntax is <ControllerClass>:<function_pattern>.
180 ## To enable access to raw_files put `FilesController:raw`.
181 ## To enable access to raw_files put `FilesController:raw`.
181 ## To enable access to patches add `ChangesetController:changeset_patch`.
182 ## To enable access to patches add `ChangesetController:changeset_patch`.
182 ## The list should be "," separated and on a single line.
183 ## The list should be "," separated and on a single line.
183 ##
184 ##
184 ## Recommended controllers to enable:
185 ## Recommended controllers to enable:
185 # ChangesetController:changeset_patch,
186 # ChangesetController:changeset_patch,
186 # ChangesetController:changeset_raw,
187 # ChangesetController:changeset_raw,
187 # FilesController:raw,
188 # FilesController:raw,
188 # FilesController:archivefile,
189 # FilesController:archivefile,
189 # GistsController:*,
190 # GistsController:*,
190 api_access_controllers_whitelist =
191 api_access_controllers_whitelist =
191
192
192 ## default encoding used to convert from and to unicode
193 ## default encoding used to convert from and to unicode
193 ## can be also a comma separated list of encoding in case of mixed encodings
194 ## can be also a comma separated list of encoding in case of mixed encodings
194 default_encoding = UTF-8
195 default_encoding = UTF-8
195
196
196 ## instance-id prefix
197 ## instance-id prefix
197 ## a prefix key for this instance used for cache invalidation when running
198 ## a prefix key for this instance used for cache invalidation when running
198 ## multiple instances of rhodecode, make sure it's globally unique for
199 ## multiple instances of rhodecode, make sure it's globally unique for
199 ## all running rhodecode instances. Leave empty if you don't use it
200 ## all running rhodecode instances. Leave empty if you don't use it
200 instance_id =
201 instance_id =
201
202
203 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
204 ## of an authentication plugin also if it is disabled by it's settings.
205 ## This could be useful if you are unable to log in to the system due to broken
206 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
207 ## module to log in again and fix the settings.
208 ##
209 ## Available builtin plugin IDs (hash is part of the ID):
210 ## egg:rhodecode-enterprise-ce#rhodecode
211 ## egg:rhodecode-enterprise-ce#pam
212 ## egg:rhodecode-enterprise-ce#ldap
213 ## egg:rhodecode-enterprise-ce#jasig_cas
214 ## egg:rhodecode-enterprise-ce#headers
215 ## egg:rhodecode-enterprise-ce#crowd
216 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
217
202 ## alternative return HTTP header for failed authentication. Default HTTP
218 ## alternative return HTTP header for failed authentication. Default HTTP
203 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
219 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
204 ## handling that causing a series of failed authentication calls.
220 ## handling that causing a series of failed authentication calls.
205 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
221 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
206 ## This will be served instead of default 401 on bad authnetication
222 ## This will be served instead of default 401 on bad authnetication
207 auth_ret_code =
223 auth_ret_code =
208
224
209 ## use special detection method when serving auth_ret_code, instead of serving
225 ## use special detection method when serving auth_ret_code, instead of serving
210 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
226 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
211 ## and then serve auth_ret_code to clients
227 ## and then serve auth_ret_code to clients
212 auth_ret_code_detection = false
228 auth_ret_code_detection = false
213
229
214 ## locking return code. When repository is locked return this HTTP code. 2XX
230 ## locking return code. When repository is locked return this HTTP code. 2XX
215 ## codes don't break the transactions while 4XX codes do
231 ## codes don't break the transactions while 4XX codes do
216 lock_ret_code = 423
232 lock_ret_code = 423
217
233
218 ## allows to change the repository location in settings page
234 ## allows to change the repository location in settings page
219 allow_repo_location_change = true
235 allow_repo_location_change = true
220
236
221 ## allows to setup custom hooks in settings page
237 ## allows to setup custom hooks in settings page
222 allow_custom_hooks_settings = true
238 allow_custom_hooks_settings = true
223
239
224 ## generated license token, goto license page in RhodeCode settings to obtain
240 ## generated license token, goto license page in RhodeCode settings to obtain
225 ## new token
241 ## new token
226 license_token =
242 license_token =
227
243
228 ## supervisor connection uri, for managing supervisor and logs.
244 ## supervisor connection uri, for managing supervisor and logs.
229 supervisor.uri =
245 supervisor.uri =
230 ## supervisord group name/id we only want this RC instance to handle
246 ## supervisord group name/id we only want this RC instance to handle
231 supervisor.group_id = dev
247 supervisor.group_id = dev
232
248
233 ## Display extended labs settings
249 ## Display extended labs settings
234 labs_settings_active = true
250 labs_settings_active = true
235
251
236 ####################################
252 ####################################
237 ### CELERY CONFIG ####
253 ### CELERY CONFIG ####
238 ####################################
254 ####################################
239 use_celery = false
255 use_celery = false
240 broker.host = localhost
256 broker.host = localhost
241 broker.vhost = rabbitmqhost
257 broker.vhost = rabbitmqhost
242 broker.port = 5672
258 broker.port = 5672
243 broker.user = rabbitmq
259 broker.user = rabbitmq
244 broker.password = qweqwe
260 broker.password = qweqwe
245
261
246 celery.imports = rhodecode.lib.celerylib.tasks
262 celery.imports = rhodecode.lib.celerylib.tasks
247
263
248 celery.result.backend = amqp
264 celery.result.backend = amqp
249 celery.result.dburi = amqp://
265 celery.result.dburi = amqp://
250 celery.result.serialier = json
266 celery.result.serialier = json
251
267
252 #celery.send.task.error.emails = true
268 #celery.send.task.error.emails = true
253 #celery.amqp.task.result.expires = 18000
269 #celery.amqp.task.result.expires = 18000
254
270
255 celeryd.concurrency = 2
271 celeryd.concurrency = 2
256 #celeryd.log.file = celeryd.log
272 #celeryd.log.file = celeryd.log
257 celeryd.log.level = debug
273 celeryd.log.level = debug
258 celeryd.max.tasks.per.child = 1
274 celeryd.max.tasks.per.child = 1
259
275
260 ## tasks will never be sent to the queue, but executed locally instead.
276 ## tasks will never be sent to the queue, but executed locally instead.
261 celery.always.eager = false
277 celery.always.eager = false
262
278
263 ####################################
279 ####################################
264 ### BEAKER CACHE ####
280 ### BEAKER CACHE ####
265 ####################################
281 ####################################
266 # default cache dir for templates. Putting this into a ramdisk
282 # default cache dir for templates. Putting this into a ramdisk
267 ## can boost performance, eg. %(here)s/data_ramdisk
283 ## can boost performance, eg. %(here)s/data_ramdisk
268 cache_dir = %(here)s/data
284 cache_dir = %(here)s/data
269
285
270 ## locking and default file storage for Beaker. Putting this into a ramdisk
286 ## locking and default file storage for Beaker. Putting this into a ramdisk
271 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
287 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
272 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
288 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
273 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
289 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
274
290
275 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
291 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
276
292
277 beaker.cache.super_short_term.type = memory
293 beaker.cache.super_short_term.type = memory
278 beaker.cache.super_short_term.expire = 10
294 beaker.cache.super_short_term.expire = 10
279 beaker.cache.super_short_term.key_length = 256
295 beaker.cache.super_short_term.key_length = 256
280
296
281 beaker.cache.short_term.type = memory
297 beaker.cache.short_term.type = memory
282 beaker.cache.short_term.expire = 60
298 beaker.cache.short_term.expire = 60
283 beaker.cache.short_term.key_length = 256
299 beaker.cache.short_term.key_length = 256
284
300
285 beaker.cache.long_term.type = memory
301 beaker.cache.long_term.type = memory
286 beaker.cache.long_term.expire = 36000
302 beaker.cache.long_term.expire = 36000
287 beaker.cache.long_term.key_length = 256
303 beaker.cache.long_term.key_length = 256
288
304
289 beaker.cache.sql_cache_short.type = memory
305 beaker.cache.sql_cache_short.type = memory
290 beaker.cache.sql_cache_short.expire = 10
306 beaker.cache.sql_cache_short.expire = 10
291 beaker.cache.sql_cache_short.key_length = 256
307 beaker.cache.sql_cache_short.key_length = 256
292
308
293 # default is memory cache, configure only if required
309 # default is memory cache, configure only if required
294 # using multi-node or multi-worker setup
310 # using multi-node or multi-worker setup
295 #beaker.cache.auth_plugins.type = ext:database
311 #beaker.cache.auth_plugins.type = ext:database
296 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
312 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
297 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
313 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
298 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
314 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
299 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
315 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
300 #beaker.cache.auth_plugins.sa.pool_size = 10
316 #beaker.cache.auth_plugins.sa.pool_size = 10
301 #beaker.cache.auth_plugins.sa.max_overflow = 0
317 #beaker.cache.auth_plugins.sa.max_overflow = 0
302
318
303 beaker.cache.repo_cache_long.type = memorylru_base
319 beaker.cache.repo_cache_long.type = memorylru_base
304 beaker.cache.repo_cache_long.max_items = 4096
320 beaker.cache.repo_cache_long.max_items = 4096
305 beaker.cache.repo_cache_long.expire = 2592000
321 beaker.cache.repo_cache_long.expire = 2592000
306
322
307 # default is memorylru_base cache, configure only if required
323 # default is memorylru_base cache, configure only if required
308 # using multi-node or multi-worker setup
324 # using multi-node or multi-worker setup
309 #beaker.cache.repo_cache_long.type = ext:memcached
325 #beaker.cache.repo_cache_long.type = ext:memcached
310 #beaker.cache.repo_cache_long.url = localhost:11211
326 #beaker.cache.repo_cache_long.url = localhost:11211
311 #beaker.cache.repo_cache_long.expire = 1209600
327 #beaker.cache.repo_cache_long.expire = 1209600
312 #beaker.cache.repo_cache_long.key_length = 256
328 #beaker.cache.repo_cache_long.key_length = 256
313
329
314 ####################################
330 ####################################
315 ### BEAKER SESSION ####
331 ### BEAKER SESSION ####
316 ####################################
332 ####################################
317
333
318 ## .session.type is type of storage options for the session, current allowed
334 ## .session.type is type of storage options for the session, current allowed
319 ## types are file, ext:memcached, ext:database, and memory(default).
335 ## types are file, ext:memcached, ext:database, and memory (default).
320 beaker.session.type = file
336 beaker.session.type = file
321 beaker.session.data_dir = %(here)s/data/sessions/data
337 beaker.session.data_dir = %(here)s/data/sessions/data
322
338
323 ## db based session, fast, and allows easy management over logged in users ##
339 ## db based session, fast, and allows easy management over logged in users ##
324 #beaker.session.type = ext:database
340 #beaker.session.type = ext:database
325 #beaker.session.table_name = db_session
341 #beaker.session.table_name = db_session
326 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
342 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
327 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
343 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
328 #beaker.session.sa.pool_recycle = 3600
344 #beaker.session.sa.pool_recycle = 3600
329 #beaker.session.sa.echo = false
345 #beaker.session.sa.echo = false
330
346
331 beaker.session.key = rhodecode
347 beaker.session.key = rhodecode
332 beaker.session.secret = develop-rc-uytcxaz
348 beaker.session.secret = develop-rc-uytcxaz
333 beaker.session.lock_dir = %(here)s/data/sessions/lock
349 beaker.session.lock_dir = %(here)s/data/sessions/lock
334
350
335 ## Secure encrypted cookie. Requires AES and AES python libraries
351 ## Secure encrypted cookie. Requires AES and AES python libraries
336 ## you must disable beaker.session.secret to use this
352 ## you must disable beaker.session.secret to use this
337 #beaker.session.encrypt_key = <key_for_encryption>
353 #beaker.session.encrypt_key = <key_for_encryption>
338 #beaker.session.validate_key = <validation_key>
354 #beaker.session.validate_key = <validation_key>
339
355
340 ## sets session as invalid(also logging out user) if it haven not been
356 ## sets session as invalid(also logging out user) if it haven not been
341 ## accessed for given amount of time in seconds
357 ## accessed for given amount of time in seconds
342 beaker.session.timeout = 2592000
358 beaker.session.timeout = 2592000
343 beaker.session.httponly = true
359 beaker.session.httponly = true
344 #beaker.session.cookie_path = /<your-prefix>
360 #beaker.session.cookie_path = /<your-prefix>
345
361
346 ## uncomment for https secure cookie
362 ## uncomment for https secure cookie
347 beaker.session.secure = false
363 beaker.session.secure = false
348
364
349 ## auto save the session to not to use .save()
365 ## auto save the session to not to use .save()
350 beaker.session.auto = false
366 beaker.session.auto = false
351
367
352 ## default cookie expiration time in seconds, set to `true` to set expire
368 ## default cookie expiration time in seconds, set to `true` to set expire
353 ## at browser close
369 ## at browser close
354 #beaker.session.cookie_expires = 3600
370 #beaker.session.cookie_expires = 3600
355
371
356 ###################################
372 ###################################
357 ## SEARCH INDEXING CONFIGURATION ##
373 ## SEARCH INDEXING CONFIGURATION ##
358 ###################################
374 ###################################
375 ## Full text search indexer is available in rhodecode-tools under
376 ## `rhodecode-tools index` command
359
377
378 # WHOOSH Backend, doesn't require additional services to run
379 # it works good with few dozen repos
360 search.module = rhodecode.lib.index.whoosh
380 search.module = rhodecode.lib.index.whoosh
361 search.location = %(here)s/data/index
381 search.location = %(here)s/data/index
362
382
383
363 ###################################
384 ###################################
364 ## ERROR AND LOG HANDLING SYSTEM ##
385 ## APPENLIGHT CONFIG ##
365 ###################################
386 ###################################
366
387
367 ## Appenlight is tailored to work with RhodeCode, see
388 ## Appenlight is tailored to work with RhodeCode, see
368 ## http://appenlight.com for details how to obtain an account
389 ## http://appenlight.com for details how to obtain an account
369
390
370 ## appenlight integration enabled
391 ## appenlight integration enabled
371 appenlight = false
392 appenlight = false
372
393
373 appenlight.server_url = https://api.appenlight.com
394 appenlight.server_url = https://api.appenlight.com
374 appenlight.api_key = YOUR_API_KEY
395 appenlight.api_key = YOUR_API_KEY
375 ;appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
396 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
376
397
377 # used for JS client
398 # used for JS client
378 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
399 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
379
400
380 ## TWEAK AMOUNT OF INFO SENT HERE
401 ## TWEAK AMOUNT OF INFO SENT HERE
381
402
382 ## enables 404 error logging (default False)
403 ## enables 404 error logging (default False)
383 appenlight.report_404 = false
404 appenlight.report_404 = false
384
405
385 ## time in seconds after request is considered being slow (default 1)
406 ## time in seconds after request is considered being slow (default 1)
386 appenlight.slow_request_time = 1
407 appenlight.slow_request_time = 1
387
408
388 ## record slow requests in application
409 ## record slow requests in application
389 ## (needs to be enabled for slow datastore recording and time tracking)
410 ## (needs to be enabled for slow datastore recording and time tracking)
390 appenlight.slow_requests = true
411 appenlight.slow_requests = true
391
412
392 ## enable hooking to application loggers
413 ## enable hooking to application loggers
393 appenlight.logging = true
414 appenlight.logging = true
394
415
395 ## minimum log level for log capture
416 ## minimum log level for log capture
396 appenlight.logging.level = WARNING
417 appenlight.logging.level = WARNING
397
418
398 ## send logs only from erroneous/slow requests
419 ## send logs only from erroneous/slow requests
399 ## (saves API quota for intensive logging)
420 ## (saves API quota for intensive logging)
400 appenlight.logging_on_error = false
421 appenlight.logging_on_error = false
401
422
402 ## list of additonal keywords that should be grabbed from environ object
423 ## list of additonal keywords that should be grabbed from environ object
403 ## can be string with comma separated list of words in lowercase
424 ## can be string with comma separated list of words in lowercase
404 ## (by default client will always send following info:
425 ## (by default client will always send following info:
405 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
426 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
406 ## start with HTTP* this list be extended with additional keywords here
427 ## start with HTTP* this list be extended with additional keywords here
407 appenlight.environ_keys_whitelist =
428 appenlight.environ_keys_whitelist =
408
429
409 ## list of keywords that should be blanked from request object
430 ## list of keywords that should be blanked from request object
410 ## can be string with comma separated list of words in lowercase
431 ## can be string with comma separated list of words in lowercase
411 ## (by default client will always blank keys that contain following words
432 ## (by default client will always blank keys that contain following words
412 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
433 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
413 ## this list be extended with additional keywords set here
434 ## this list be extended with additional keywords set here
414 appenlight.request_keys_blacklist =
435 appenlight.request_keys_blacklist =
415
436
416 ## list of namespaces that should be ignores when gathering log entries
437 ## list of namespaces that should be ignores when gathering log entries
417 ## can be string with comma separated list of namespaces
438 ## can be string with comma separated list of namespaces
418 ## (by default the client ignores own entries: appenlight_client.client)
439 ## (by default the client ignores own entries: appenlight_client.client)
419 appenlight.log_namespace_blacklist =
440 appenlight.log_namespace_blacklist =
420
441
421
442
422 ################################################################################
443 ################################################################################
423 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
444 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
424 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
445 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
425 ## execute malicious code after an exception is raised. ##
446 ## execute malicious code after an exception is raised. ##
426 ################################################################################
447 ################################################################################
427 #set debug = false
448 #set debug = false
428
449
429
450
430 ##############
451 ##############
431 ## STYLING ##
452 ## STYLING ##
432 ##############
453 ##############
433 debug_style = true
454 debug_style = true
434
455
435 #########################################################
456 #########################################################
436 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
457 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
437 #########################################################
458 #########################################################
438 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
459 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
439 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
460 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
440 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
461 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
441
462
442 # see sqlalchemy docs for other advanced settings
463 # see sqlalchemy docs for other advanced settings
443
464
444 ## print the sql statements to output
465 ## print the sql statements to output
445 sqlalchemy.db1.echo = false
466 sqlalchemy.db1.echo = false
446 ## recycle the connections after this ammount of seconds
467 ## recycle the connections after this ammount of seconds
447 sqlalchemy.db1.pool_recycle = 3600
468 sqlalchemy.db1.pool_recycle = 3600
448 sqlalchemy.db1.convert_unicode = true
469 sqlalchemy.db1.convert_unicode = true
449
470
450 ## the number of connections to keep open inside the connection pool.
471 ## the number of connections to keep open inside the connection pool.
451 ## 0 indicates no limit
472 ## 0 indicates no limit
452 #sqlalchemy.db1.pool_size = 5
473 #sqlalchemy.db1.pool_size = 5
453
474
454 ## the number of connections to allow in connection pool "overflow", that is
475 ## the number of connections to allow in connection pool "overflow", that is
455 ## connections that can be opened above and beyond the pool_size setting,
476 ## connections that can be opened above and beyond the pool_size setting,
456 ## which defaults to five.
477 ## which defaults to five.
457 #sqlalchemy.db1.max_overflow = 10
478 #sqlalchemy.db1.max_overflow = 10
458
479
459
480
460 ##################
481 ##################
461 ### VCS CONFIG ###
482 ### VCS CONFIG ###
462 ##################
483 ##################
463 vcs.server.enable = true
484 vcs.server.enable = true
464 vcs.server = localhost:9900
485 vcs.server = localhost:9900
465 # Available protocols: pyro4, http
466 vcs.server.protocol = pyro4
467
486
468 # available impl:
487 ## Web server connectivity protocol, responsible for web based VCS operatations
469 # vcsserver.scm_app (EE only, for testing),
488 ## Available protocols are:
470 # rhodecode.lib.middleware.utils.scm_app_http
489 ## `pyro4` - using pyro4 server
471 # pyro4
490 ## `http` - using http-rpc backend
491 #vcs.server.protocol = http
492
493 ## Push/Pull operations protocol, available options are:
494 ## `pyro4` - using pyro4 server
495 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
496 ## `vcsserver.scm_app` - internal app (EE only)
472 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
497 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
473
498
499 ## Push/Pull operations hooks protocol, available options are:
500 ## `pyro4` - using pyro4 server
501 ## `http` - using http-rpc backend
502 #vcs.hooks.protocol = http
503
474 vcs.server.log_level = debug
504 vcs.server.log_level = debug
505 ## Start VCSServer with this instance as a subprocess, usefull for development
475 vcs.start_server = true
506 vcs.start_server = true
476 vcs.backends = hg, git, svn
507 vcs.backends = hg, git, svn
477 vcs.connection_timeout = 3600
508 vcs.connection_timeout = 3600
478 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
509 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
479 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
510 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
480 #vcs.svn.compatible_version = pre-1.8-compatible
511 #vcs.svn.compatible_version = pre-1.8-compatible
481
512
482 ################################
513 ################################
483 ### LOGGING CONFIGURATION ####
514 ### LOGGING CONFIGURATION ####
484 ################################
515 ################################
485 [loggers]
516 [loggers]
486 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
517 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
487
518
488 [handlers]
519 [handlers]
489 keys = console, console_sql
520 keys = console, console_sql
490
521
491 [formatters]
522 [formatters]
492 keys = generic, color_formatter, color_formatter_sql
523 keys = generic, color_formatter, color_formatter_sql
493
524
494 #############
525 #############
495 ## LOGGERS ##
526 ## LOGGERS ##
496 #############
527 #############
497 [logger_root]
528 [logger_root]
498 level = NOTSET
529 level = NOTSET
499 handlers = console
530 handlers = console
500
531
501 [logger_routes]
532 [logger_routes]
502 level = DEBUG
533 level = DEBUG
503 handlers =
534 handlers =
504 qualname = routes.middleware
535 qualname = routes.middleware
505 ## "level = DEBUG" logs the route matched and routing variables.
536 ## "level = DEBUG" logs the route matched and routing variables.
506 propagate = 1
537 propagate = 1
507
538
508 [logger_beaker]
539 [logger_beaker]
509 level = DEBUG
540 level = DEBUG
510 handlers =
541 handlers =
511 qualname = beaker.container
542 qualname = beaker.container
512 propagate = 1
543 propagate = 1
513
544
514 [logger_pyro4]
545 [logger_pyro4]
515 level = DEBUG
546 level = DEBUG
516 handlers =
547 handlers =
517 qualname = Pyro4
548 qualname = Pyro4
518 propagate = 1
549 propagate = 1
519
550
520 [logger_templates]
551 [logger_templates]
521 level = INFO
552 level = INFO
522 handlers =
553 handlers =
523 qualname = pylons.templating
554 qualname = pylons.templating
524 propagate = 1
555 propagate = 1
525
556
526 [logger_rhodecode]
557 [logger_rhodecode]
527 level = DEBUG
558 level = DEBUG
528 handlers =
559 handlers =
529 qualname = rhodecode
560 qualname = rhodecode
530 propagate = 1
561 propagate = 1
531
562
532 [logger_sqlalchemy]
563 [logger_sqlalchemy]
533 level = INFO
564 level = INFO
534 handlers = console_sql
565 handlers = console_sql
535 qualname = sqlalchemy.engine
566 qualname = sqlalchemy.engine
536 propagate = 0
567 propagate = 0
537
568
538 [logger_whoosh_indexer]
569 [logger_whoosh_indexer]
539 level = DEBUG
570 level = DEBUG
540 handlers =
571 handlers =
541 qualname = whoosh_indexer
572 qualname = whoosh_indexer
542 propagate = 1
573 propagate = 1
543
574
544 ##############
575 ##############
545 ## HANDLERS ##
576 ## HANDLERS ##
546 ##############
577 ##############
547
578
548 [handler_console]
579 [handler_console]
549 class = StreamHandler
580 class = StreamHandler
550 args = (sys.stderr,)
581 args = (sys.stderr,)
551 level = DEBUG
582 level = DEBUG
552 formatter = color_formatter
583 formatter = color_formatter
553
584
554 [handler_console_sql]
585 [handler_console_sql]
555 class = StreamHandler
586 class = StreamHandler
556 args = (sys.stderr,)
587 args = (sys.stderr,)
557 level = DEBUG
588 level = DEBUG
558 formatter = color_formatter_sql
589 formatter = color_formatter_sql
559
590
560 ################
591 ################
561 ## FORMATTERS ##
592 ## FORMATTERS ##
562 ################
593 ################
563
594
564 [formatter_generic]
595 [formatter_generic]
565 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
596 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
566 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
597 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
567 datefmt = %Y-%m-%d %H:%M:%S
598 datefmt = %Y-%m-%d %H:%M:%S
568
599
569 [formatter_color_formatter]
600 [formatter_color_formatter]
570 class = rhodecode.lib.logging_formatter.ColorFormatter
601 class = rhodecode.lib.logging_formatter.ColorFormatter
571 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
602 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
572 datefmt = %Y-%m-%d %H:%M:%S
603 datefmt = %Y-%m-%d %H:%M:%S
573
604
574 [formatter_color_formatter_sql]
605 [formatter_color_formatter_sql]
575 class = rhodecode.lib.logging_formatter.ColorFormatterSql
606 class = rhodecode.lib.logging_formatter.ColorFormatterSql
576 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
607 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
577 datefmt = %Y-%m-%d %H:%M:%S
608 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,551 +1,577 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode Enterprise - configuration file #
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 # Built-in functions and variables #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
6 # #
7 ################################################################################
7 ################################################################################
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 pdebug = false
11 pdebug = false
12 ################################################################################
12 ################################################################################
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17 #email_to = admin@localhost
17 #email_to = admin@localhost
18 #error_email_from = paste_error@localhost
18 #error_email_from = paste_error@localhost
19 #app_email_from = rhodecode-noreply@localhost
19 #app_email_from = rhodecode-noreply@localhost
20 #error_message =
20 #error_message =
21 #email_prefix = [RhodeCode]
21 #email_prefix = [RhodeCode]
22
22
23 #smtp_server = mail.server.com
23 #smtp_server = mail.server.com
24 #smtp_username =
24 #smtp_username =
25 #smtp_password =
25 #smtp_password =
26 #smtp_port =
26 #smtp_port =
27 #smtp_use_tls = false
27 #smtp_use_tls = false
28 #smtp_use_ssl = true
28 #smtp_use_ssl = true
29 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
30 #smtp_auth =
30 #smtp_auth =
31
31
32 [server:main]
32 [server:main]
33 ## COMMON ##
33 ## COMMON ##
34 host = 127.0.0.1
34 host = 127.0.0.1
35 port = 5000
35 port = 5000
36
36
37 ##########################
37 ##################################
38 ## WAITRESS WSGI SERVER ##
38 ## WAITRESS WSGI SERVER ##
39 ##########################
39 ## Recommended for Development ##
40 use = egg:waitress#main
40 ##################################
41 #use = egg:waitress#main
41 ## number of worker threads
42 ## number of worker threads
42 threads = 5
43 #threads = 5
43 ## MAX BODY SIZE 100GB
44 ## MAX BODY SIZE 100GB
44 max_request_body_size = 107374182400
45 #max_request_body_size = 107374182400
45 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## May not work on old windows systems.
47 ## May not work on old windows systems.
47 asyncore_use_poll = true
48 #asyncore_use_poll = true
48
49
49
50
50 ##########################
51 ##########################
51 ## GUNICORN WSGI SERVER ##
52 ## GUNICORN WSGI SERVER ##
52 ##########################
53 ##########################
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 #use = egg:gunicorn#main
55 use = egg:gunicorn#main
55 ## Sets the number of process workers. You must set `instance_id = *`
56 ## Sets the number of process workers. You must set `instance_id = *`
56 ## when this option is set to more than one worker, recommended
57 ## when this option is set to more than one worker, recommended
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 ## The `instance_id = *` must be set in the [app:main] section below
59 ## The `instance_id = *` must be set in the [app:main] section below
59 #workers = 1
60 workers = 2
60 ## number of threads for each of the worker, must be set to 1 for gevent
61 ## number of threads for each of the worker, must be set to 1 for gevent
61 ## generally recommened to be at 1
62 ## generally recommened to be at 1
62 #threads = 1
63 #threads = 1
63 ## process name
64 ## process name
64 #proc_name = rhodecode
65 proc_name = rhodecode
65 ## type of worker class, one of sync, gevent
66 ## type of worker class, one of sync, gevent
66 ## recommended for bigger setup is using of of other than sync one
67 ## recommended for bigger setup is using of of other than sync one
67 #worker_class = sync
68 worker_class = sync
68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 ## The maximum number of simultaneous clients. Valid only for Gevent
69 #worker_connections = 10
70 #worker_connections = 10
70 ## max number of requests that worker will handle before being gracefully
71 ## max number of requests that worker will handle before being gracefully
71 ## restarted, could prevent memory leaks
72 ## restarted, could prevent memory leaks
72 #max_requests = 1000
73 max_requests = 1000
73 #max_requests_jitter = 30
74 max_requests_jitter = 30
74 ## ammount of time a worker can spend with handling a request before it
75 ## amount of time a worker can spend with handling a request before it
75 ## gets killed and restarted. Set to 6hrs
76 ## gets killed and restarted. Set to 6hrs
76 #timeout = 21600
77 timeout = 21600
77
78
78
79
79 ## prefix middleware for RhodeCode, disables force_https flag.
80 ## prefix middleware for RhodeCode, disables force_https flag.
80 ## allows to set RhodeCode under a prefix in server.
81 ## allows to set RhodeCode under a prefix in server.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
83 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
84 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
85 #prefix = /<your-prefix>
85
86
86 [app:main]
87 [app:main]
87 use = egg:rhodecode-enterprise-ce
88 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
89 ## enable proxy prefix middleware, defined below
89 #filter-with = proxy-prefix
90 #filter-with = proxy-prefix
90
91
91 full_stack = true
92 full_stack = true
92
93
93 ## Serve static files via RhodeCode, disable to serve them via HTTP server
94 ## Serve static files via RhodeCode, disable to serve them via HTTP server
94 static_files = true
95 static_files = true
95
96
96 ## Optional Languages
97 ## Optional Languages
97 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
98 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
98 lang = en
99 lang = en
99
100
100 ## perform a full repository scan on each server start, this should be
101 ## perform a full repository scan on each server start, this should be
101 ## set to false after first startup, to allow faster server restarts.
102 ## set to false after first startup, to allow faster server restarts.
102 startup.import_repos = false
103 startup.import_repos = false
103
104
104 ## Uncomment and set this path to use archive download cache.
105 ## Uncomment and set this path to use archive download cache.
105 ## Once enabled, generated archives will be cached at this location
106 ## Once enabled, generated archives will be cached at this location
106 ## and served from the cache during subsequent requests for the same archive of
107 ## and served from the cache during subsequent requests for the same archive of
107 ## the repository.
108 ## the repository.
108 #archive_cache_dir = /tmp/tarballcache
109 #archive_cache_dir = /tmp/tarballcache
109
110
110 ## change this to unique ID for security
111 ## change this to unique ID for security
111 app_instance_uuid = rc-production
112 app_instance_uuid = rc-production
112
113
113 ## cut off limit for large diffs (size in bytes)
114 ## cut off limit for large diffs (size in bytes)
114 cut_off_limit_diff = 1024000
115 cut_off_limit_diff = 1024000
115 cut_off_limit_file = 256000
116 cut_off_limit_file = 256000
116
117
117 ## use cache version of scm repo everywhere
118 ## use cache version of scm repo everywhere
118 vcs_full_cache = true
119 vcs_full_cache = true
119
120
120 ## force https in RhodeCode, fixes https redirects, assumes it's always https
121 ## force https in RhodeCode, fixes https redirects, assumes it's always https
121 ## Normally this is controlled by proper http flags sent from http server
122 ## Normally this is controlled by proper http flags sent from http server
122 force_https = false
123 force_https = false
123
124
124 ## use Strict-Transport-Security headers
125 ## use Strict-Transport-Security headers
125 use_htsts = false
126 use_htsts = false
126
127
127 ## number of commits stats will parse on each iteration
128 ## number of commits stats will parse on each iteration
128 commit_parse_limit = 25
129 commit_parse_limit = 25
129
130
130 ## git rev filter option, --all is the default filter, if you need to
131 ## git rev filter option, --all is the default filter, if you need to
131 ## hide all refs in changelog switch this to --branches --tags
132 ## hide all refs in changelog switch this to --branches --tags
132 git_rev_filter = --branches --tags
133 git_rev_filter = --branches --tags
133
134
134 # Set to true if your repos are exposed using the dumb protocol
135 # Set to true if your repos are exposed using the dumb protocol
135 git_update_server_info = false
136 git_update_server_info = false
136
137
137 ## RSS/ATOM feed options
138 ## RSS/ATOM feed options
138 rss_cut_off_limit = 256000
139 rss_cut_off_limit = 256000
139 rss_items_per_page = 10
140 rss_items_per_page = 10
140 rss_include_diff = false
141 rss_include_diff = false
141
142
142 ## gist URL alias, used to create nicer urls for gist. This should be an
143 ## gist URL alias, used to create nicer urls for gist. This should be an
143 ## url that does rewrites to _admin/gists/<gistid>.
144 ## url that does rewrites to _admin/gists/<gistid>.
144 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
145 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
145 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
146 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
146 gist_alias_url =
147 gist_alias_url =
147
148
148 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
149 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
149 ## used for access.
150 ## used for access.
150 ## Adding ?auth_token = <token> to the url authenticates this request as if it
151 ## Adding ?auth_token = <token> to the url authenticates this request as if it
151 ## came from the the logged in user who own this authentication token.
152 ## came from the the logged in user who own this authentication token.
152 ##
153 ##
153 ## Syntax is <ControllerClass>:<function_pattern>.
154 ## Syntax is <ControllerClass>:<function_pattern>.
154 ## To enable access to raw_files put `FilesController:raw`.
155 ## To enable access to raw_files put `FilesController:raw`.
155 ## To enable access to patches add `ChangesetController:changeset_patch`.
156 ## To enable access to patches add `ChangesetController:changeset_patch`.
156 ## The list should be "," separated and on a single line.
157 ## The list should be "," separated and on a single line.
157 ##
158 ##
158 ## Recommended controllers to enable:
159 ## Recommended controllers to enable:
159 # ChangesetController:changeset_patch,
160 # ChangesetController:changeset_patch,
160 # ChangesetController:changeset_raw,
161 # ChangesetController:changeset_raw,
161 # FilesController:raw,
162 # FilesController:raw,
162 # FilesController:archivefile,
163 # FilesController:archivefile,
163 # GistsController:*,
164 # GistsController:*,
164 api_access_controllers_whitelist =
165 api_access_controllers_whitelist =
165
166
166 ## default encoding used to convert from and to unicode
167 ## default encoding used to convert from and to unicode
167 ## can be also a comma separated list of encoding in case of mixed encodings
168 ## can be also a comma separated list of encoding in case of mixed encodings
168 default_encoding = UTF-8
169 default_encoding = UTF-8
169
170
170 ## instance-id prefix
171 ## instance-id prefix
171 ## a prefix key for this instance used for cache invalidation when running
172 ## a prefix key for this instance used for cache invalidation when running
172 ## multiple instances of rhodecode, make sure it's globally unique for
173 ## multiple instances of rhodecode, make sure it's globally unique for
173 ## all running rhodecode instances. Leave empty if you don't use it
174 ## all running rhodecode instances. Leave empty if you don't use it
174 instance_id =
175 instance_id =
175
176
177 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
178 ## of an authentication plugin also if it is disabled by it's settings.
179 ## This could be useful if you are unable to log in to the system due to broken
180 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
181 ## module to log in again and fix the settings.
182 ##
183 ## Available builtin plugin IDs (hash is part of the ID):
184 ## egg:rhodecode-enterprise-ce#rhodecode
185 ## egg:rhodecode-enterprise-ce#pam
186 ## egg:rhodecode-enterprise-ce#ldap
187 ## egg:rhodecode-enterprise-ce#jasig_cas
188 ## egg:rhodecode-enterprise-ce#headers
189 ## egg:rhodecode-enterprise-ce#crowd
190 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
191
176 ## alternative return HTTP header for failed authentication. Default HTTP
192 ## alternative return HTTP header for failed authentication. Default HTTP
177 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
193 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
178 ## handling that causing a series of failed authentication calls.
194 ## handling that causing a series of failed authentication calls.
179 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
195 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
180 ## This will be served instead of default 401 on bad authnetication
196 ## This will be served instead of default 401 on bad authnetication
181 auth_ret_code =
197 auth_ret_code =
182
198
183 ## use special detection method when serving auth_ret_code, instead of serving
199 ## use special detection method when serving auth_ret_code, instead of serving
184 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
200 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
185 ## and then serve auth_ret_code to clients
201 ## and then serve auth_ret_code to clients
186 auth_ret_code_detection = false
202 auth_ret_code_detection = false
187
203
188 ## locking return code. When repository is locked return this HTTP code. 2XX
204 ## locking return code. When repository is locked return this HTTP code. 2XX
189 ## codes don't break the transactions while 4XX codes do
205 ## codes don't break the transactions while 4XX codes do
190 lock_ret_code = 423
206 lock_ret_code = 423
191
207
192 ## allows to change the repository location in settings page
208 ## allows to change the repository location in settings page
193 allow_repo_location_change = true
209 allow_repo_location_change = true
194
210
195 ## allows to setup custom hooks in settings page
211 ## allows to setup custom hooks in settings page
196 allow_custom_hooks_settings = true
212 allow_custom_hooks_settings = true
197
213
198 ## generated license token, goto license page in RhodeCode settings to obtain
214 ## generated license token, goto license page in RhodeCode settings to obtain
199 ## new token
215 ## new token
200 license_token =
216 license_token =
201
217
202 ## supervisor connection uri, for managing supervisor and logs.
218 ## supervisor connection uri, for managing supervisor and logs.
203 supervisor.uri =
219 supervisor.uri =
204 ## supervisord group name/id we only want this RC instance to handle
220 ## supervisord group name/id we only want this RC instance to handle
205 supervisor.group_id = prod
221 supervisor.group_id = prod
206
222
207 ## Display extended labs settings
223 ## Display extended labs settings
208 labs_settings_active = true
224 labs_settings_active = true
209
225
210 ####################################
226 ####################################
211 ### CELERY CONFIG ####
227 ### CELERY CONFIG ####
212 ####################################
228 ####################################
213 use_celery = false
229 use_celery = false
214 broker.host = localhost
230 broker.host = localhost
215 broker.vhost = rabbitmqhost
231 broker.vhost = rabbitmqhost
216 broker.port = 5672
232 broker.port = 5672
217 broker.user = rabbitmq
233 broker.user = rabbitmq
218 broker.password = qweqwe
234 broker.password = qweqwe
219
235
220 celery.imports = rhodecode.lib.celerylib.tasks
236 celery.imports = rhodecode.lib.celerylib.tasks
221
237
222 celery.result.backend = amqp
238 celery.result.backend = amqp
223 celery.result.dburi = amqp://
239 celery.result.dburi = amqp://
224 celery.result.serialier = json
240 celery.result.serialier = json
225
241
226 #celery.send.task.error.emails = true
242 #celery.send.task.error.emails = true
227 #celery.amqp.task.result.expires = 18000
243 #celery.amqp.task.result.expires = 18000
228
244
229 celeryd.concurrency = 2
245 celeryd.concurrency = 2
230 #celeryd.log.file = celeryd.log
246 #celeryd.log.file = celeryd.log
231 celeryd.log.level = debug
247 celeryd.log.level = debug
232 celeryd.max.tasks.per.child = 1
248 celeryd.max.tasks.per.child = 1
233
249
234 ## tasks will never be sent to the queue, but executed locally instead.
250 ## tasks will never be sent to the queue, but executed locally instead.
235 celery.always.eager = false
251 celery.always.eager = false
236
252
237 ####################################
253 ####################################
238 ### BEAKER CACHE ####
254 ### BEAKER CACHE ####
239 ####################################
255 ####################################
240 # default cache dir for templates. Putting this into a ramdisk
256 # default cache dir for templates. Putting this into a ramdisk
241 ## can boost performance, eg. %(here)s/data_ramdisk
257 ## can boost performance, eg. %(here)s/data_ramdisk
242 cache_dir = %(here)s/data
258 cache_dir = %(here)s/data
243
259
244 ## locking and default file storage for Beaker. Putting this into a ramdisk
260 ## locking and default file storage for Beaker. Putting this into a ramdisk
245 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
261 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
246 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
262 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
247 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
263 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
248
264
249 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
265 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
250
266
251 beaker.cache.super_short_term.type = memory
267 beaker.cache.super_short_term.type = memory
252 beaker.cache.super_short_term.expire = 10
268 beaker.cache.super_short_term.expire = 10
253 beaker.cache.super_short_term.key_length = 256
269 beaker.cache.super_short_term.key_length = 256
254
270
255 beaker.cache.short_term.type = memory
271 beaker.cache.short_term.type = memory
256 beaker.cache.short_term.expire = 60
272 beaker.cache.short_term.expire = 60
257 beaker.cache.short_term.key_length = 256
273 beaker.cache.short_term.key_length = 256
258
274
259 beaker.cache.long_term.type = memory
275 beaker.cache.long_term.type = memory
260 beaker.cache.long_term.expire = 36000
276 beaker.cache.long_term.expire = 36000
261 beaker.cache.long_term.key_length = 256
277 beaker.cache.long_term.key_length = 256
262
278
263 beaker.cache.sql_cache_short.type = memory
279 beaker.cache.sql_cache_short.type = memory
264 beaker.cache.sql_cache_short.expire = 10
280 beaker.cache.sql_cache_short.expire = 10
265 beaker.cache.sql_cache_short.key_length = 256
281 beaker.cache.sql_cache_short.key_length = 256
266
282
267 # default is memory cache, configure only if required
283 # default is memory cache, configure only if required
268 # using multi-node or multi-worker setup
284 # using multi-node or multi-worker setup
269 #beaker.cache.auth_plugins.type = ext:database
285 #beaker.cache.auth_plugins.type = ext:database
270 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
286 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
271 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
287 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
272 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
288 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
273 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
289 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
274 #beaker.cache.auth_plugins.sa.pool_size = 10
290 #beaker.cache.auth_plugins.sa.pool_size = 10
275 #beaker.cache.auth_plugins.sa.max_overflow = 0
291 #beaker.cache.auth_plugins.sa.max_overflow = 0
276
292
277 beaker.cache.repo_cache_long.type = memorylru_base
293 beaker.cache.repo_cache_long.type = memorylru_base
278 beaker.cache.repo_cache_long.max_items = 4096
294 beaker.cache.repo_cache_long.max_items = 4096
279 beaker.cache.repo_cache_long.expire = 2592000
295 beaker.cache.repo_cache_long.expire = 2592000
280
296
281 # default is memorylru_base cache, configure only if required
297 # default is memorylru_base cache, configure only if required
282 # using multi-node or multi-worker setup
298 # using multi-node or multi-worker setup
283 #beaker.cache.repo_cache_long.type = ext:memcached
299 #beaker.cache.repo_cache_long.type = ext:memcached
284 #beaker.cache.repo_cache_long.url = localhost:11211
300 #beaker.cache.repo_cache_long.url = localhost:11211
285 #beaker.cache.repo_cache_long.expire = 1209600
301 #beaker.cache.repo_cache_long.expire = 1209600
286 #beaker.cache.repo_cache_long.key_length = 256
302 #beaker.cache.repo_cache_long.key_length = 256
287
303
288 ####################################
304 ####################################
289 ### BEAKER SESSION ####
305 ### BEAKER SESSION ####
290 ####################################
306 ####################################
291
307
292 ## .session.type is type of storage options for the session, current allowed
308 ## .session.type is type of storage options for the session, current allowed
293 ## types are file, ext:memcached, ext:database, and memory(default).
309 ## types are file, ext:memcached, ext:database, and memory (default).
294 beaker.session.type = file
310 beaker.session.type = file
295 beaker.session.data_dir = %(here)s/data/sessions/data
311 beaker.session.data_dir = %(here)s/data/sessions/data
296
312
297 ## db based session, fast, and allows easy management over logged in users ##
313 ## db based session, fast, and allows easy management over logged in users ##
298 #beaker.session.type = ext:database
314 #beaker.session.type = ext:database
299 #beaker.session.table_name = db_session
315 #beaker.session.table_name = db_session
300 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
316 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
301 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
317 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
302 #beaker.session.sa.pool_recycle = 3600
318 #beaker.session.sa.pool_recycle = 3600
303 #beaker.session.sa.echo = false
319 #beaker.session.sa.echo = false
304
320
305 beaker.session.key = rhodecode
321 beaker.session.key = rhodecode
306 beaker.session.secret = production-rc-uytcxaz
322 beaker.session.secret = production-rc-uytcxaz
307 #beaker.session.lock_dir = %(here)s/data/sessions/lock
323 beaker.session.lock_dir = %(here)s/data/sessions/lock
308
324
309 ## Secure encrypted cookie. Requires AES and AES python libraries
325 ## Secure encrypted cookie. Requires AES and AES python libraries
310 ## you must disable beaker.session.secret to use this
326 ## you must disable beaker.session.secret to use this
311 #beaker.session.encrypt_key = <key_for_encryption>
327 #beaker.session.encrypt_key = <key_for_encryption>
312 #beaker.session.validate_key = <validation_key>
328 #beaker.session.validate_key = <validation_key>
313
329
314 ## sets session as invalid(also logging out user) if it haven not been
330 ## sets session as invalid(also logging out user) if it haven not been
315 ## accessed for given amount of time in seconds
331 ## accessed for given amount of time in seconds
316 beaker.session.timeout = 2592000
332 beaker.session.timeout = 2592000
317 beaker.session.httponly = true
333 beaker.session.httponly = true
318 #beaker.session.cookie_path = /<your-prefix>
334 #beaker.session.cookie_path = /<your-prefix>
319
335
320 ## uncomment for https secure cookie
336 ## uncomment for https secure cookie
321 beaker.session.secure = false
337 beaker.session.secure = false
322
338
323 ## auto save the session to not to use .save()
339 ## auto save the session to not to use .save()
324 beaker.session.auto = false
340 beaker.session.auto = false
325
341
326 ## default cookie expiration time in seconds, set to `true` to set expire
342 ## default cookie expiration time in seconds, set to `true` to set expire
327 ## at browser close
343 ## at browser close
328 #beaker.session.cookie_expires = 3600
344 #beaker.session.cookie_expires = 3600
329
345
330 ###################################
346 ###################################
331 ## SEARCH INDEXING CONFIGURATION ##
347 ## SEARCH INDEXING CONFIGURATION ##
332 ###################################
348 ###################################
349 ## Full text search indexer is available in rhodecode-tools under
350 ## `rhodecode-tools index` command
333
351
352 # WHOOSH Backend, doesn't require additional services to run
353 # it works good with few dozen repos
334 search.module = rhodecode.lib.index.whoosh
354 search.module = rhodecode.lib.index.whoosh
335 search.location = %(here)s/data/index
355 search.location = %(here)s/data/index
336
356
357
337 ###################################
358 ###################################
338 ## ERROR AND LOG HANDLING SYSTEM ##
359 ## APPENLIGHT CONFIG ##
339 ###################################
360 ###################################
340
361
341 ## Appenlight is tailored to work with RhodeCode, see
362 ## Appenlight is tailored to work with RhodeCode, see
342 ## http://appenlight.com for details how to obtain an account
363 ## http://appenlight.com for details how to obtain an account
343
364
344 ## appenlight integration enabled
365 ## appenlight integration enabled
345 appenlight = false
366 appenlight = false
346
367
347 appenlight.server_url = https://api.appenlight.com
368 appenlight.server_url = https://api.appenlight.com
348 appenlight.api_key = YOUR_API_KEY
369 appenlight.api_key = YOUR_API_KEY
349 ;appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
370 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
350
371
351 # used for JS client
372 # used for JS client
352 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
373 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
353
374
354 ## TWEAK AMOUNT OF INFO SENT HERE
375 ## TWEAK AMOUNT OF INFO SENT HERE
355
376
356 ## enables 404 error logging (default False)
377 ## enables 404 error logging (default False)
357 appenlight.report_404 = false
378 appenlight.report_404 = false
358
379
359 ## time in seconds after request is considered being slow (default 1)
380 ## time in seconds after request is considered being slow (default 1)
360 appenlight.slow_request_time = 1
381 appenlight.slow_request_time = 1
361
382
362 ## record slow requests in application
383 ## record slow requests in application
363 ## (needs to be enabled for slow datastore recording and time tracking)
384 ## (needs to be enabled for slow datastore recording and time tracking)
364 appenlight.slow_requests = true
385 appenlight.slow_requests = true
365
386
366 ## enable hooking to application loggers
387 ## enable hooking to application loggers
367 appenlight.logging = true
388 appenlight.logging = true
368
389
369 ## minimum log level for log capture
390 ## minimum log level for log capture
370 appenlight.logging.level = WARNING
391 appenlight.logging.level = WARNING
371
392
372 ## send logs only from erroneous/slow requests
393 ## send logs only from erroneous/slow requests
373 ## (saves API quota for intensive logging)
394 ## (saves API quota for intensive logging)
374 appenlight.logging_on_error = false
395 appenlight.logging_on_error = false
375
396
376 ## list of additonal keywords that should be grabbed from environ object
397 ## list of additonal keywords that should be grabbed from environ object
377 ## can be string with comma separated list of words in lowercase
398 ## can be string with comma separated list of words in lowercase
378 ## (by default client will always send following info:
399 ## (by default client will always send following info:
379 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
400 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
380 ## start with HTTP* this list be extended with additional keywords here
401 ## start with HTTP* this list be extended with additional keywords here
381 appenlight.environ_keys_whitelist =
402 appenlight.environ_keys_whitelist =
382
403
383 ## list of keywords that should be blanked from request object
404 ## list of keywords that should be blanked from request object
384 ## can be string with comma separated list of words in lowercase
405 ## can be string with comma separated list of words in lowercase
385 ## (by default client will always blank keys that contain following words
406 ## (by default client will always blank keys that contain following words
386 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
407 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
387 ## this list be extended with additional keywords set here
408 ## this list be extended with additional keywords set here
388 appenlight.request_keys_blacklist =
409 appenlight.request_keys_blacklist =
389
410
390 ## list of namespaces that should be ignores when gathering log entries
411 ## list of namespaces that should be ignores when gathering log entries
391 ## can be string with comma separated list of namespaces
412 ## can be string with comma separated list of namespaces
392 ## (by default the client ignores own entries: appenlight_client.client)
413 ## (by default the client ignores own entries: appenlight_client.client)
393 appenlight.log_namespace_blacklist =
414 appenlight.log_namespace_blacklist =
394
415
395
416
396 ################################################################################
417 ################################################################################
397 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
418 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
398 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
419 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
399 ## execute malicious code after an exception is raised. ##
420 ## execute malicious code after an exception is raised. ##
400 ################################################################################
421 ################################################################################
401 set debug = false
422 set debug = false
402
423
403
424
404 ##############
405 ## STYLING ##
406 ##############
407 debug_style = false
408
409 #########################################################
425 #########################################################
410 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
426 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
411 #########################################################
427 #########################################################
412 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
428 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
413 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
429 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
414 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
430 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
415
431
416 # see sqlalchemy docs for other advanced settings
432 # see sqlalchemy docs for other advanced settings
417
433
418 ## print the sql statements to output
434 ## print the sql statements to output
419 sqlalchemy.db1.echo = false
435 sqlalchemy.db1.echo = false
420 ## recycle the connections after this ammount of seconds
436 ## recycle the connections after this ammount of seconds
421 sqlalchemy.db1.pool_recycle = 3600
437 sqlalchemy.db1.pool_recycle = 3600
422 sqlalchemy.db1.convert_unicode = true
438 sqlalchemy.db1.convert_unicode = true
423
439
424 ## the number of connections to keep open inside the connection pool.
440 ## the number of connections to keep open inside the connection pool.
425 ## 0 indicates no limit
441 ## 0 indicates no limit
426 #sqlalchemy.db1.pool_size = 5
442 #sqlalchemy.db1.pool_size = 5
427
443
428 ## the number of connections to allow in connection pool "overflow", that is
444 ## the number of connections to allow in connection pool "overflow", that is
429 ## connections that can be opened above and beyond the pool_size setting,
445 ## connections that can be opened above and beyond the pool_size setting,
430 ## which defaults to five.
446 ## which defaults to five.
431 #sqlalchemy.db1.max_overflow = 10
447 #sqlalchemy.db1.max_overflow = 10
432
448
433
449
434 ##################
450 ##################
435 ### VCS CONFIG ###
451 ### VCS CONFIG ###
436 ##################
452 ##################
437 vcs.server.enable = true
453 vcs.server.enable = true
438 vcs.server = localhost:9900
454 vcs.server = localhost:9900
439 # Available protocols: pyro4, http
440 vcs.server.protocol = pyro4
441
455
442 # available impl:
456 ## Web server connectivity protocol, responsible for web based VCS operatations
443 # vcsserver.scm_app (EE only, for testing),
457 ## Available protocols are:
444 # rhodecode.lib.middleware.utils.scm_app_http
458 ## `pyro4` - using pyro4 server
445 # pyro4
459 ## `http` - using http-rpc backend
460 #vcs.server.protocol = http
461
462 ## Push/Pull operations protocol, available options are:
463 ## `pyro4` - using pyro4 server
464 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
465 ## `vcsserver.scm_app` - internal app (EE only)
446 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
466 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
447
467
468 ## Push/Pull operations hooks protocol, available options are:
469 ## `pyro4` - using pyro4 server
470 ## `http` - using http-rpc backend
471 #vcs.hooks.protocol = http
472
448 vcs.server.log_level = info
473 vcs.server.log_level = info
474 ## Start VCSServer with this instance as a subprocess, usefull for development
449 vcs.start_server = false
475 vcs.start_server = false
450 vcs.backends = hg, git, svn
476 vcs.backends = hg, git, svn
451 vcs.connection_timeout = 3600
477 vcs.connection_timeout = 3600
452 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
478 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
453 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
479 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
454 #vcs.svn.compatible_version = pre-1.8-compatible
480 #vcs.svn.compatible_version = pre-1.8-compatible
455
481
456 ################################
482 ################################
457 ### LOGGING CONFIGURATION ####
483 ### LOGGING CONFIGURATION ####
458 ################################
484 ################################
459 [loggers]
485 [loggers]
460 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
486 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
461
487
462 [handlers]
488 [handlers]
463 keys = console, console_sql
489 keys = console, console_sql
464
490
465 [formatters]
491 [formatters]
466 keys = generic, color_formatter, color_formatter_sql
492 keys = generic, color_formatter, color_formatter_sql
467
493
468 #############
494 #############
469 ## LOGGERS ##
495 ## LOGGERS ##
470 #############
496 #############
471 [logger_root]
497 [logger_root]
472 level = NOTSET
498 level = NOTSET
473 handlers = console
499 handlers = console
474
500
475 [logger_routes]
501 [logger_routes]
476 level = DEBUG
502 level = DEBUG
477 handlers =
503 handlers =
478 qualname = routes.middleware
504 qualname = routes.middleware
479 ## "level = DEBUG" logs the route matched and routing variables.
505 ## "level = DEBUG" logs the route matched and routing variables.
480 propagate = 1
506 propagate = 1
481
507
482 [logger_beaker]
508 [logger_beaker]
483 level = DEBUG
509 level = DEBUG
484 handlers =
510 handlers =
485 qualname = beaker.container
511 qualname = beaker.container
486 propagate = 1
512 propagate = 1
487
513
488 [logger_pyro4]
514 [logger_pyro4]
489 level = DEBUG
515 level = DEBUG
490 handlers =
516 handlers =
491 qualname = Pyro4
517 qualname = Pyro4
492 propagate = 1
518 propagate = 1
493
519
494 [logger_templates]
520 [logger_templates]
495 level = INFO
521 level = INFO
496 handlers =
522 handlers =
497 qualname = pylons.templating
523 qualname = pylons.templating
498 propagate = 1
524 propagate = 1
499
525
500 [logger_rhodecode]
526 [logger_rhodecode]
501 level = DEBUG
527 level = DEBUG
502 handlers =
528 handlers =
503 qualname = rhodecode
529 qualname = rhodecode
504 propagate = 1
530 propagate = 1
505
531
506 [logger_sqlalchemy]
532 [logger_sqlalchemy]
507 level = INFO
533 level = INFO
508 handlers = console_sql
534 handlers = console_sql
509 qualname = sqlalchemy.engine
535 qualname = sqlalchemy.engine
510 propagate = 0
536 propagate = 0
511
537
512 [logger_whoosh_indexer]
538 [logger_whoosh_indexer]
513 level = DEBUG
539 level = DEBUG
514 handlers =
540 handlers =
515 qualname = whoosh_indexer
541 qualname = whoosh_indexer
516 propagate = 1
542 propagate = 1
517
543
518 ##############
544 ##############
519 ## HANDLERS ##
545 ## HANDLERS ##
520 ##############
546 ##############
521
547
522 [handler_console]
548 [handler_console]
523 class = StreamHandler
549 class = StreamHandler
524 args = (sys.stderr,)
550 args = (sys.stderr,)
525 level = INFO
551 level = INFO
526 formatter = generic
552 formatter = generic
527
553
528 [handler_console_sql]
554 [handler_console_sql]
529 class = StreamHandler
555 class = StreamHandler
530 args = (sys.stderr,)
556 args = (sys.stderr,)
531 level = WARN
557 level = WARN
532 formatter = generic
558 formatter = generic
533
559
534 ################
560 ################
535 ## FORMATTERS ##
561 ## FORMATTERS ##
536 ################
562 ################
537
563
538 [formatter_generic]
564 [formatter_generic]
539 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
565 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
540 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
566 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
541 datefmt = %Y-%m-%d %H:%M:%S
567 datefmt = %Y-%m-%d %H:%M:%S
542
568
543 [formatter_color_formatter]
569 [formatter_color_formatter]
544 class = rhodecode.lib.logging_formatter.ColorFormatter
570 class = rhodecode.lib.logging_formatter.ColorFormatter
545 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
571 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
546 datefmt = %Y-%m-%d %H:%M:%S
572 datefmt = %Y-%m-%d %H:%M:%S
547
573
548 [formatter_color_formatter_sql]
574 [formatter_color_formatter_sql]
549 class = rhodecode.lib.logging_formatter.ColorFormatterSql
575 class = rhodecode.lib.logging_formatter.ColorFormatterSql
550 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
576 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
551 datefmt = %Y-%m-%d %H:%M:%S
577 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,215 +1,219 b''
1 # Nix environment for the community edition
1 # Nix environment for the community edition
2 #
2 #
3 # This shall be as lean as possible, just producing the Enterprise
3 # This shall be as lean as possible, just producing the Enterprise
4 # derivation. For advanced tweaks to pimp up the development environment we use
4 # derivation. For advanced tweaks to pimp up the development environment we use
5 # "shell.nix" so that it does not have to clutter this file.
5 # "shell.nix" so that it does not have to clutter this file.
6
6
7 { pkgs ? (import <nixpkgs> {})
7 { pkgs ? (import <nixpkgs> {})
8 , pythonPackages ? "python27Packages"
8 , pythonPackages ? "python27Packages"
9 , pythonExternalOverrides ? self: super: {}
9 , pythonExternalOverrides ? self: super: {}
10 , doCheck ? true
10 , doCheck ? true
11 }:
11 }:
12
12
13 let pkgs_ = pkgs; in
13 let pkgs_ = pkgs; in
14
14
15 let
15 let
16 pkgs = pkgs_.overridePackages (self: super: {
16 pkgs = pkgs_.overridePackages (self: super: {
17 # Override subversion derivation to
17 # Override subversion derivation to
18 # - activate python bindings
18 # - activate python bindings
19 # - set version to 1.8
19 # - set version to 1.8
20 subversion = super.subversion18.override {
20 subversion = super.subversion18.override {
21 httpSupport = true;
21 httpSupport = true;
22 pythonBindings = true;
22 pythonBindings = true;
23 python = self.python27Packages.python;
23 python = self.python27Packages.python;
24 };
24 };
25 });
25 });
26
26
27 inherit (pkgs.lib) fix extends;
27 inherit (pkgs.lib) fix extends;
28
28
29 basePythonPackages = with builtins; if isAttrs pythonPackages
29 basePythonPackages = with builtins; if isAttrs pythonPackages
30 then pythonPackages
30 then pythonPackages
31 else getAttr pythonPackages pkgs;
31 else getAttr pythonPackages pkgs;
32
32
33 elem = builtins.elem;
33 elem = builtins.elem;
34 basename = path: with pkgs.lib; last (splitString "/" path);
34 basename = path: with pkgs.lib; last (splitString "/" path);
35 startsWith = prefix: full: let
35 startsWith = prefix: full: let
36 actualPrefix = builtins.substring 0 (builtins.stringLength prefix) full;
36 actualPrefix = builtins.substring 0 (builtins.stringLength prefix) full;
37 in actualPrefix == prefix;
37 in actualPrefix == prefix;
38
38
39 src-filter = path: type: with pkgs.lib;
39 src-filter = path: type: with pkgs.lib;
40 let
40 let
41 ext = last (splitString "." path);
41 ext = last (splitString "." path);
42 in
42 in
43 !elem (basename path) [
43 !elem (basename path) [
44 ".git" ".hg" "__pycache__" ".eggs" "node_modules"
44 ".git" ".hg" "__pycache__" ".eggs" "node_modules"
45 "build" "data" "tmp"] &&
45 "build" "data" "tmp"] &&
46 !elem ext ["egg-info" "pyc"] &&
46 !elem ext ["egg-info" "pyc"] &&
47 !startsWith "result" path;
47 !startsWith "result" path;
48
48
49 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
49 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
50
50
51 # Load the generated node packages
51 # Load the generated node packages
52 nodePackages = pkgs.callPackage "${pkgs.path}/pkgs/top-level/node-packages.nix" rec {
52 nodePackages = pkgs.callPackage "${pkgs.path}/pkgs/top-level/node-packages.nix" rec {
53 self = nodePackages;
53 self = nodePackages;
54 generated = pkgs.callPackage ./pkgs/node-packages.nix { inherit self; };
54 generated = pkgs.callPackage ./pkgs/node-packages.nix { inherit self; };
55 };
55 };
56
56
57 # TODO: Should be taken automatically out of the generates packages.
57 # TODO: Should be taken automatically out of the generates packages.
58 # apps.nix has one solution for this, although I'd prefer to have the deps
58 # apps.nix has one solution for this, although I'd prefer to have the deps
59 # from package.json mapped in here.
59 # from package.json mapped in here.
60 nodeDependencies = with nodePackages; [
60 nodeDependencies = with nodePackages; [
61 grunt
61 grunt
62 grunt-contrib-concat
62 grunt-contrib-concat
63 grunt-contrib-jshint
63 grunt-contrib-jshint
64 grunt-contrib-less
64 grunt-contrib-less
65 grunt-contrib-watch
65 grunt-contrib-watch
66 jshint
66 jshint
67 ];
67 ];
68
68
69 pythonGeneratedPackages = self: basePythonPackages.override (a: {
69 pythonGeneratedPackages = self: basePythonPackages.override (a: {
70 inherit self;
70 inherit self;
71 })
71 })
72 // (scopedImport {
72 // (scopedImport {
73 self = self;
73 self = self;
74 super = basePythonPackages;
74 super = basePythonPackages;
75 inherit pkgs;
75 inherit pkgs;
76 inherit (pkgs) fetchurl fetchgit;
76 inherit (pkgs) fetchurl fetchgit;
77 } ./pkgs/python-packages.nix);
77 } ./pkgs/python-packages.nix);
78
78
79 pythonOverrides = import ./pkgs/python-packages-overrides.nix {
79 pythonOverrides = import ./pkgs/python-packages-overrides.nix {
80 inherit
80 inherit
81 basePythonPackages
81 basePythonPackages
82 pkgs;
82 pkgs;
83 };
83 };
84
84
85 pythonLocalOverrides = self: super: {
85 pythonLocalOverrides = self: super: {
86 rhodecode-enterprise-ce =
86 rhodecode-enterprise-ce =
87 let
87 let
88 version = "${builtins.readFile ./rhodecode/VERSION}";
88 version = builtins.readFile ./rhodecode/VERSION;
89 linkNodeModules = ''
89 linkNodeModules = ''
90 echo "Link node packages"
90 echo "Link node packages"
91 # TODO: check if this adds stuff as a dependency, closure size
91 # TODO: check if this adds stuff as a dependency, closure size
92 rm -fr node_modules
92 rm -fr node_modules
93 mkdir -p node_modules
93 mkdir -p node_modules
94 ${pkgs.lib.concatMapStrings (dep: ''
94 ${pkgs.lib.concatMapStrings (dep: ''
95 ln -sfv ${dep}/lib/node_modules/${dep.pkgName} node_modules/
95 ln -sfv ${dep}/lib/node_modules/${dep.pkgName} node_modules/
96 '') nodeDependencies}
96 '') nodeDependencies}
97 echo "DONE: Link node packages"
97 echo "DONE: Link node packages"
98 '';
98 '';
99 in super.rhodecode-enterprise-ce.override (attrs: {
99 in super.rhodecode-enterprise-ce.override (attrs: {
100
100
101 inherit doCheck;
101 inherit doCheck;
102 name = "rhodecode-enterprise-ce-${version}";
102 name = "rhodecode-enterprise-ce-${version}";
103 version = version;
103 version = version;
104 src = rhodecode-enterprise-ce-src;
104 src = rhodecode-enterprise-ce-src;
105
105
106 buildInputs =
106 buildInputs =
107 attrs.buildInputs ++
107 attrs.buildInputs ++
108 (with self; [
108 (with self; [
109 pkgs.nodePackages.grunt-cli
109 pkgs.nodePackages.grunt-cli
110 pkgs.subversion
110 pkgs.subversion
111 pytest-catchlog
111 pytest-catchlog
112 rc_testdata
112 rc_testdata
113 ]);
113 ]);
114
114
115 propagatedBuildInputs = attrs.propagatedBuildInputs ++ (with self; [
115 propagatedBuildInputs = attrs.propagatedBuildInputs ++ (with self; [
116 rhodecode-tools
116 rhodecode-tools
117 ]);
117 ]);
118
118
119 # TODO: johbo: Make a nicer way to expose the parts. Maybe
119 # TODO: johbo: Make a nicer way to expose the parts. Maybe
120 # pkgs/default.nix?
120 # pkgs/default.nix?
121 passthru = {
121 passthru = {
122 inherit myPythonPackagesUnfix;
122 inherit
123 pythonLocalOverrides
124 myPythonPackagesUnfix;
123 pythonPackages = self;
125 pythonPackages = self;
124 };
126 };
125
127
126 LC_ALL = "en_US.UTF-8";
128 LC_ALL = "en_US.UTF-8";
127 LOCALE_ARCHIVE =
129 LOCALE_ARCHIVE =
128 if pkgs.stdenv ? glibc
130 if pkgs.stdenv ? glibc
129 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
131 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
130 else "";
132 else "";
131
133
132 # Somewhat snappier setup of the development environment
134 # Somewhat snappier setup of the development environment
133 # TODO: move into shell.nix
135 # TODO: move into shell.nix
134 # TODO: think of supporting a stable path again, so that multiple shells
136 # TODO: think of supporting a stable path again, so that multiple shells
135 # can share it.
137 # can share it.
136 shellHook = ''
138 shellHook = ''
137 tmp_path=$(mktemp -d)
139 tmp_path=$(mktemp -d)
138 export PATH="$tmp_path/bin:$PATH"
140 export PATH="$tmp_path/bin:$PATH"
139 export PYTHONPATH="$tmp_path/${self.python.sitePackages}:$PYTHONPATH"
141 export PYTHONPATH="$tmp_path/${self.python.sitePackages}:$PYTHONPATH"
140 mkdir -p $tmp_path/${self.python.sitePackages}
142 mkdir -p $tmp_path/${self.python.sitePackages}
141 python setup.py develop --prefix $tmp_path --allow-hosts ""
143 python setup.py develop --prefix $tmp_path --allow-hosts ""
142 '' + linkNodeModules;
144 '' + linkNodeModules;
143
145
144 preCheck = ''
146 preCheck = ''
145 export PATH="$out/bin:$PATH"
147 export PATH="$out/bin:$PATH"
146 '';
148 '';
147
149
148 postCheck = ''
150 postCheck = ''
149 rm -rf $out/lib/${self.python.libPrefix}/site-packages/pytest_pylons
151 rm -rf $out/lib/${self.python.libPrefix}/site-packages/pytest_pylons
150 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
152 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
151 '';
153 '';
152
154
153 preBuild = linkNodeModules + ''
155 preBuild = linkNodeModules + ''
154 grunt
156 grunt
155 rm -fr node_modules
157 rm -fr node_modules
156 '';
158 '';
157
159
158 postInstall = ''
160 postInstall = ''
159 # python based programs need to be wrapped
161 # python based programs need to be wrapped
160 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
162 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
161 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
163 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
162 ln -s ${self.PasteScript}/bin/paster $out/bin/
164 ln -s ${self.PasteScript}/bin/paster $out/bin/
165 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
163
166
164 # rhodecode-tools
167 # rhodecode-tools
165 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
168 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
166 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
169 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
167
170
168 # note that condition should be restricted when adding further tools
171 # note that condition should be restricted when adding further tools
169 for file in $out/bin/*; do #*/
172 for file in $out/bin/*; do #*/
170 wrapProgram $file \
173 wrapProgram $file \
171 --prefix PYTHONPATH : $PYTHONPATH \
174 --prefix PYTHONPATH : $PYTHONPATH \
175 --prefix PATH : $PATH \
172 --set PYTHONHASHSEED random
176 --set PYTHONHASHSEED random
173 done
177 done
174
178
175 mkdir $out/etc
179 mkdir $out/etc
176 cp configs/production.ini $out/etc
180 cp configs/production.ini $out/etc
177
181
178 echo "Writing meta information for rccontrol to nix-support/rccontrol"
182 echo "Writing meta information for rccontrol to nix-support/rccontrol"
179 mkdir -p $out/nix-support/rccontrol
183 mkdir -p $out/nix-support/rccontrol
180 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
184 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
181 echo "DONE: Meta information for rccontrol written"
185 echo "DONE: Meta information for rccontrol written"
182
186
183 # TODO: johbo: Make part of ac-tests
187 # TODO: johbo: Make part of ac-tests
184 if [ ! -f rhodecode/public/js/scripts.js ]; then
188 if [ ! -f rhodecode/public/js/scripts.js ]; then
185 echo "Missing scripts.js"
189 echo "Missing scripts.js"
186 exit 1
190 exit 1
187 fi
191 fi
188 if [ ! -f rhodecode/public/css/style.css ]; then
192 if [ ! -f rhodecode/public/css/style.css ]; then
189 echo "Missing style.css"
193 echo "Missing style.css"
190 exit 1
194 exit 1
191 fi
195 fi
192 '';
196 '';
193
197
194 });
198 });
195
199
196 rc_testdata = self.buildPythonPackage rec {
200 rc_testdata = self.buildPythonPackage rec {
197 name = "rc_testdata-0.7.0";
201 name = "rc_testdata-0.7.0";
198 src = pkgs.fetchhg {
202 src = pkgs.fetchhg {
199 url = "https://code.rhodecode.com/upstream/rc_testdata";
203 url = "https://code.rhodecode.com/upstream/rc_testdata";
200 rev = "v0.7.0";
204 rev = "v0.7.0";
201 sha256 = "0w3z0zn8lagr707v67lgys23sl6pbi4xg7pfvdbw58h3q384h6rx";
205 sha256 = "0w3z0zn8lagr707v67lgys23sl6pbi4xg7pfvdbw58h3q384h6rx";
202 };
206 };
203 };
207 };
204
208
205 };
209 };
206
210
207 # Apply all overrides and fix the final package set
211 # Apply all overrides and fix the final package set
208 myPythonPackagesUnfix =
212 myPythonPackagesUnfix =
209 (extends pythonExternalOverrides
213 (extends pythonExternalOverrides
210 (extends pythonLocalOverrides
214 (extends pythonLocalOverrides
211 (extends pythonOverrides
215 (extends pythonOverrides
212 pythonGeneratedPackages)));
216 pythonGeneratedPackages)));
213 myPythonPackages = (fix myPythonPackagesUnfix);
217 myPythonPackages = (fix myPythonPackagesUnfix);
214
218
215 in myPythonPackages.rhodecode-enterprise-ce
219 in myPythonPackages.rhodecode-enterprise-ce
@@ -1,33 +1,33 b''
1 Apache Reverse Proxy
1 Apache Reverse Proxy
2 ^^^^^^^^^^^^^^^^^^^^
2 ^^^^^^^^^^^^^^^^^^^^
3
3
4 Here is a sample configuration file for using Apache as a reverse proxy.
4 Here is a sample configuration file for using Apache as a reverse proxy.
5
5
6 .. code-block:: apache
6 .. code-block:: apache
7
7
8 <VirtualHost *:80>
8 <VirtualHost *:80>
9 ServerName hg.myserver.com
9 ServerName hg.myserver.com
10 ServerAlias hg.myserver.com
10 ServerAlias hg.myserver.com
11
11
12 ## uncomment root directive if you want to serve static files by nginx
12 ## uncomment root directive if you want to serve static files by
13 ## requires static_files = false in .ini file
13 ## Apache requires static_files = false in .ini file
14 DocumentRoot /path/to/installation/rhodecode/public
14 #DocumentRoot /path/to/rhodecode/installation/public
15
15
16 <Proxy *>
16 <Proxy *>
17 Order allow,deny
17 Order allow,deny
18 Allow from all
18 Allow from all
19 </Proxy>
19 </Proxy>
20
20
21 #important !
21 ## Important !
22 #Directive to properly generate url (clone url) for pylons
22 ## Directive to properly generate url (clone url) for pylons
23 ProxyPreserveHost On
23 ProxyPreserveHost On
24
24
25 #rhodecode instance
25 ## RhodeCode instance running
26 ProxyPass / http://127.0.0.1:5000/
26 ProxyPass / http://127.0.0.1:10002/
27 ProxyPassReverse / http://127.0.0.1:5000/
27 ProxyPassReverse / http://127.0.0.1:10002/
28
28
29 #to enable https use line below
29 ## to enable https use line below
30 #SetEnvIf X-Url-Scheme https HTTPS=1
30 #SetEnvIf X-Url-Scheme https HTTPS=1
31
31
32 </VirtualHost>
32 </VirtualHost>
33
33
@@ -1,235 +1,272 b''
1 .. _indexing-ref:
1 .. _indexing-ref:
2
2
3 Full-text Search
3 Full-text Search
4 ----------------
4 ----------------
5
5
6 By default |RCM| uses `Whoosh`_ to index |repos| and provide full-text search.
6 By default |RC| is configured to use `Whoosh`_ to index |repos| and
7 provide full-text search.
8
9 |RCE| also provides support for `Elasticsearch`_ as a backend for scalable
10 search. See :ref:`enable-elasticsearch` for details.
11
12 Indexing
13 ^^^^^^^^
14
7 To run the indexer you need to use an |authtoken| with admin rights to all
15 To run the indexer you need to use an |authtoken| with admin rights to all
8 |repos|.
16 |repos|.
9
17
10 To index new content added, you have the option to set the indexer up in a
18 To index new content added, you have the option to set the indexer up in a
11 number of ways, for example:
19 number of ways, for example:
12
20
13 * Call the indexer via a cron job. We recommend running this nightly,
21 * Call the indexer via a cron job. We recommend running this nightly,
14 unless you need everything indexed immediately.
22 unless you need everything indexed immediately.
15 * Set the indexer to infinitely loop and reindex as soon as it has run its
23 * Set the indexer to infinitely loop and reindex as soon as it has run its
16 cycle.
24 cycle.
17 * Hook the indexer up with your CI server to reindex after each push.
25 * Hook the indexer up with your CI server to reindex after each push.
18
26
19 The indexer works by indexing new commits added since the last run. If you
27 The indexer works by indexing new commits added since the last run. If you
20 wish to build a brand new index from scratch each time,
28 wish to build a brand new index from scratch each time,
21 use the ``force`` option in the configuration file.
29 use the ``force`` option in the configuration file.
22
30
23 .. important::
31 .. important::
24
32
25 You need to have |RCT| installed, see :ref:`install-tools`. Since |RCE|
33 You need to have |RCT| installed, see :ref:`install-tools`. Since |RCE|
26 3.5.0 they are installed by default.
34 3.5.0 they are installed by default.
27
35
28 To set up indexing, use the following steps:
36 To set up indexing, use the following steps:
29
37
30 1. :ref:`config-rhoderc`, if running tools remotely.
38 1. :ref:`config-rhoderc`, if running tools remotely.
31 2. :ref:`run-index`
39 2. :ref:`run-index`
32 3. :ref:`set-index`
40 3. :ref:`set-index`
33 4. :ref:`advanced-indexing`
41 4. :ref:`advanced-indexing`
34
42
35 .. _config-rhoderc:
43 .. _config-rhoderc:
36
44
37 Configure the ``.rhoderc`` File
45 Configure the ``.rhoderc`` File
38 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39
47
40 |RCT| uses the :file:`/home/{user}/.rhoderc` file for connection details
48 |RCT| uses the :file:`/home/{user}/.rhoderc` file for connection details
41 to |RCM| instances. If this file is not automatically created,
49 to |RCM| instances. If this file is not automatically created,
42 you can configure it using the following example. You need to configure the
50 you can configure it using the following example. You need to configure the
43 details for each instance you want to index.
51 details for each instance you want to index.
44
52
45 .. code-block:: bash
53 .. code-block:: bash
46
54
47 # Check the instance details
55 # Check the instance details
48 # of the instance you want to index
56 # of the instance you want to index
49 $ rccontrol status
57 $ rccontrol status
50
58
51 - NAME: enterprise-1
59 - NAME: enterprise-1
52 - STATUS: RUNNING
60 - STATUS: RUNNING
53 - TYPE: Momentum
61 - TYPE: Momentum
54 - VERSION: 1.5.0
62 - VERSION: 1.5.0
55 - URL: http://127.0.0.1:10000
63 - URL: http://127.0.0.1:10000
56
64
57 To get your API Token, on the |RCM| interface go to
65 To get your API Token, on the |RCM| interface go to
58 :menuselection:`username --> My Account --> Auth tokens`
66 :menuselection:`username --> My Account --> Auth tokens`
59
67
60 .. code-block:: ini
68 .. code-block:: ini
61
69
62 # Configure .rhoderc with matching details
70 # Configure .rhoderc with matching details
63 # This allows the indexer to connect to the instance
71 # This allows the indexer to connect to the instance
64 [instance:enterprise-1]
72 [instance:enterprise-1]
65 api_host = http://127.0.0.1:10000
73 api_host = http://127.0.0.1:10000
66 api_key = <auth token goes here>
74 api_key = <auth token goes here>
67 repo_dir = /home/<username>/repos
75 repo_dir = /home/<username>/repos
68
76
69 .. _run-index:
77 .. _run-index:
70
78
71 Run the Indexer
79 Run the Indexer
72 ^^^^^^^^^^^^^^^
80 ^^^^^^^^^^^^^^^
73
81
74 Run the indexer using the following command, and specify the instance you
82 Run the indexer using the following command, and specify the instance you
75 want to index:
83 want to index:
76
84
77 .. code-block:: bash
85 .. code-block:: bash
78
86
79 # From inside a virtualevv
87 # From inside a virtualevv
80 (venv)$ rhodecode-index --instance-name=enterprise-1
88 (venv)$ rhodecode-index --instance-name=enterprise-1
81
89
82 # Using default installation
90 # Using default installation
83 $ /home/user/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
91 $ /home/user/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
84 --instance-name=enterprise-4
92 --instance-name=enterprise-4
85
93
86 # Using a custom mapping file
94 # Using a custom mapping file
87 $ /home/user/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
95 $ /home/user/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
88 --instance-name=enterprise-4 \
96 --instance-name=enterprise-4 \
89 --mapping=/home/user/.rccontrol/enterprise-4/mapping.ini
97 --mapping=/home/user/.rccontrol/enterprise-4/mapping.ini
90
98
91 .. note::
99 .. note::
92
100
93 |RCT| require |PY| 2.7 to run.
101 |RCT| require |PY| 2.7 to run.
94
102
95 .. _set-index:
103 .. _set-index:
96
104
97 Schedule the Indexer
105 Schedule the Indexer
98 ^^^^^^^^^^^^^^^^^^^^
106 ^^^^^^^^^^^^^^^^^^^^
99
107
100 To schedule the indexer, configure the crontab file to run the indexer inside
108 To schedule the indexer, configure the crontab file to run the indexer inside
101 your |RCT| virtualenv using the following steps.
109 your |RCT| virtualenv using the following steps.
102
110
103 1. Open the crontab file, using ``crontab -e``.
111 1. Open the crontab file, using ``crontab -e``.
104 2. Add the indexer to the crontab, and schedule it to run as regularly as you
112 2. Add the indexer to the crontab, and schedule it to run as regularly as you
105 wish.
113 wish.
106 3. Save the file.
114 3. Save the file.
107
115
108 .. code-block:: bash
116 .. code-block:: bash
109
117
110 $ crontab -e
118 $ crontab -e
111
119
112 # The virtualenv can be called using its full path, so for example you can
120 # The virtualenv can be called using its full path, so for example you can
113 # put this example into the crontab
121 # put this example into the crontab
114
122
115 # Run the indexer daily at 4am using the default mapping settings
123 # Run the indexer daily at 4am using the default mapping settings
116 * 4 * * * /home/ubuntu/.virtualenv/rhodecode-venv/bin/rhodecode-index \
124 * 4 * * * /home/ubuntu/.virtualenv/rhodecode-venv/bin/rhodecode-index \
117 --instance-name=enterprise-1
125 --instance-name=enterprise-1
118
126
119 # Run the indexer every Sunday at 3am using default mapping
127 # Run the indexer every Sunday at 3am using default mapping
120 * 3 * * 0 /home/ubuntu/.virtualenv/rhodecode-venv/bin/rhodecode-index \
128 * 3 * * 0 /home/ubuntu/.virtualenv/rhodecode-venv/bin/rhodecode-index \
121 --instance-name=enterprise-1
129 --instance-name=enterprise-1
122
130
123 # Run the indexer every 15 minutes
131 # Run the indexer every 15 minutes
124 # using a specially configured mapping file
132 # using a specially configured mapping file
125 */15 * * * * ~/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
133 */15 * * * * ~/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
126 --instance-name=enterprise-4 \
134 --instance-name=enterprise-4 \
127 --mapping=/home/user/.rccontrol/enterprise-4/mapping.ini
135 --mapping=/home/user/.rccontrol/enterprise-4/mapping.ini
128
136
129 .. _advanced-indexing:
137 .. _advanced-indexing:
130
138
131 Advanced Indexing
139 Advanced Indexing
132 ^^^^^^^^^^^^^^^^^
140 ^^^^^^^^^^^^^^^^^
133
141
134 |RCT| indexes based on the :file:`mapping.ini` file. To configure your index,
142 |RCT| indexes based on the :file:`mapping.ini` file. To configure your index,
135 you can specify different options in this file. The default location is:
143 you can specify different options in this file. The default location is:
136
144
137 * :file:`/home/{user}/.rccontrol/{instance-id}/mapping.ini`, using default
145 * :file:`/home/{user}/.rccontrol/{instance-id}/mapping.ini`, using default
138 |RCT|.
146 |RCT|.
139 * :file:`~/venv/lib/python2.7/site-packages/rhodecode_tools/templates/mapping.ini`,
147 * :file:`~/venv/lib/python2.7/site-packages/rhodecode_tools/templates/mapping.ini`,
140 when using ``virtualenv``.
148 when using ``virtualenv``.
141
149
142 .. note::
150 .. note::
143
151
144 If you need to create the :file:`mapping.ini` file, use the |RCT|
152 If you need to create the :file:`mapping.ini` file, use the |RCT|
145 ``rhodecode-index --create-mapping path/to/file`` API call. For details,
153 ``rhodecode-index --create-mapping path/to/file`` API call. For details,
146 see the :ref:`tools-cli` section.
154 see the :ref:`tools-cli` section.
147
155
148 The indexer runs in a random order to prevent a failing |repo| from stopping
156 The indexer runs in a random order to prevent a failing |repo| from stopping
149 a build. To configure different indexing scenarios, set the following options
157 a build. To configure different indexing scenarios, set the following options
150 inside the :file:`mapping.ini` and specify the altered file using the
158 inside the :file:`mapping.ini` and specify the altered file using the
151 ``--mapping`` option.
159 ``--mapping`` option.
152
160
153 * ``index_files`` : Index the specified file types.
161 * ``index_files`` : Index the specified file types.
154 * ``skip_files`` : Do not index the specified file types.
162 * ``skip_files`` : Do not index the specified file types.
155 * ``index_files_content`` : Index the content of the specified file types.
163 * ``index_files_content`` : Index the content of the specified file types.
156 * ``skip_files_content`` : Do not index the content of the specified files.
164 * ``skip_files_content`` : Do not index the content of the specified files.
157 * ``force`` : Create a fresh index on each run.
165 * ``force`` : Create a fresh index on each run.
158 * ``max_filesize`` : Files larger than the set size will not be indexed.
166 * ``max_filesize`` : Files larger than the set size will not be indexed.
159 * ``commit_parse_limit`` : Set the batch size when indexing commit messages.
167 * ``commit_parse_limit`` : Set the batch size when indexing commit messages.
160 Set to a lower number to lessen memory load.
168 Set to a lower number to lessen memory load.
161 * ``repo_limit`` : Set the maximum number or |repos| indexed per run.
169 * ``repo_limit`` : Set the maximum number or |repos| indexed per run.
162 * ``[INCLUDE]`` : Set |repos| you want indexed. This takes precedent over
170 * ``[INCLUDE]`` : Set |repos| you want indexed. This takes precedent over
163 ``[EXCLUDE]``.
171 ``[EXCLUDE]``.
164 * ``[EXCLUDE]`` : Set |repos| you do not want indexed. Exclude can be used to
172 * ``[EXCLUDE]`` : Set |repos| you do not want indexed. Exclude can be used to
165 not index branches, forks, or log |repos|.
173 not index branches, forks, or log |repos|.
166
174
167 At the end of the file you can specify conditions for specific |repos| that
175 At the end of the file you can specify conditions for specific |repos| that
168 will override the default values. To configure your indexer,
176 will override the default values. To configure your indexer,
169 use the following example :file:`mapping.ini` file.
177 use the following example :file:`mapping.ini` file.
170
178
171 .. code-block:: ini
179 .. code-block:: ini
172
180
173 [__DEFAULT__]
181 [__DEFAULT__]
174 # default patterns for indexing files and content of files.
182 # default patterns for indexing files and content of files.
175 # Binary files are skipped by default.
183 # Binary files are skipped by default.
176
184
177 # Index python and markdown files
185 # Index python and markdown files
178 index_files = *.py, *.md
186 index_files = *.py, *.md
179
187
180 # Do not index these file types
188 # Do not index these file types
181 skip_files = *.svg, *.log, *.dump, *.txt
189 skip_files = *.svg, *.log, *.dump, *.txt
182
190
183 # Index both file types and their content
191 # Index both file types and their content
184 index_files_content = *.cpp, *.ini, *.py
192 index_files_content = *.cpp, *.ini, *.py
185
193
186 # Index file names, but not file content
194 # Index file names, but not file content
187 skip_files_content = *.svg,
195 skip_files_content = *.svg,
188
196
189 # Force rebuilding an index from scratch. Each repository will be rebuild
197 # Force rebuilding an index from scratch. Each repository will be rebuild
190 # from scratch with a global flag. Use local flag to rebuild single repos
198 # from scratch with a global flag. Use local flag to rebuild single repos
191 force = false
199 force = false
192
200
193 # Do not index files larger than 385KB
201 # Do not index files larger than 385KB
194 max_filesize = 385KB
202 max_filesize = 385KB
195
203
196 # Limit commit indexing to 500 per batch
204 # Limit commit indexing to 500 per batch
197 commit_parse_limit = 500
205 commit_parse_limit = 500
198
206
199 # Limit each index run to 25 repos
207 # Limit each index run to 25 repos
200 repo_limit = 25
208 repo_limit = 25
201
209
202 # __INCLUDE__ is more important that __EXCLUDE__.
210 # __INCLUDE__ is more important that __EXCLUDE__.
203
211
204 [__INCLUDE__]
212 [__INCLUDE__]
205 # Include all repos with these names
213 # Include all repos with these names
206
214
207 docs/* = 1
215 docs/* = 1
208 lib/* = 1
216 lib/* = 1
209
217
210 [__EXCLUDE__]
218 [__EXCLUDE__]
211 # Do not include the following repo in index
219 # Do not include the following repo in index
212
220
213 dev-docs/* = 1
221 dev-docs/* = 1
214 legacy-repos/* = 1
222 legacy-repos/* = 1
215 *-dev/* = 1
223 *-dev/* = 1
216
224
217 # Each repo that needs special indexing is a separate section below.
225 # Each repo that needs special indexing is a separate section below.
218 # In each section set the options to override the global configuration
226 # In each section set the options to override the global configuration
219 # parameters above.
227 # parameters above.
220 # If special settings are not configured, the global configuration values
228 # If special settings are not configured, the global configuration values
221 # above are inherited. If no special repositories are
229 # above are inherited. If no special repositories are
222 # defined here RhodeCode will use the API to ask for all repositories
230 # defined here RhodeCode will use the API to ask for all repositories
223
231
224 # For this repo use different settings
232 # For this repo use different settings
225 [special-repo]
233 [special-repo]
226 commit_parse_limit = 20,
234 commit_parse_limit = 20,
227 skip_files = *.idea, *.xml,
235 skip_files = *.idea, *.xml,
228
236
229 # For another repo use different settings
237 # For another repo use different settings
230 [another-special-repo]
238 [another-special-repo]
231 index_files = *,
239 index_files = *,
232 max_filesize = 800MB
240 max_filesize = 800MB
233 commit_parse_limit = 20000
241 commit_parse_limit = 20000
234
242
243 .. _enable-elasticsearch:
244
245 Enabling Elasticsearch
246 ^^^^^^^^^^^^^^^^^^^^^^
247
248 1. Open the :file:`rhodecode.ini` file for the instance you wish to edit. The
249 default location is
250 :file:`home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
251 2. Find the search configuration section:
252
253 .. code-block:: ini
254
255 ###################################
256 ## SEARCH INDEXING CONFIGURATION ##
257 ###################################
258
259 search.module = rhodecode.lib.index.whoosh
260 search.location = %(here)s/data/index
261
262 and change it to:
263
264 .. code-block:: ini
265
266 search.module = rc_elasticsearch
267 search.location = http://localhost:9200/
268
269 where ``search.location`` points to the elasticsearch server.
270
235 .. _Whoosh: https://pypi.python.org/pypi/Whoosh/
271 .. _Whoosh: https://pypi.python.org/pypi/Whoosh/
272 .. _Elasticsearch: https://www.elastic.co/ No newline at end of file
@@ -1,71 +1,72 b''
1 Nginx Configuration Example
1 Nginx Configuration Example
2 ---------------------------
2 ---------------------------
3
3
4 Use the following example to configure Nginx as a your web server.
4 Use the following example to configure Nginx as a your web server.
5
5
6 .. code-block:: nginx
6 .. code-block:: nginx
7
7
8 upstream rc {
8 upstream rc {
9
9
10 server 127.0.0.1:5000;
10 server 127.0.0.1:10002;
11
11
12 # add more instances for load balancing
12 # add more instances for load balancing
13 # server 127.0.0.1:5001;
13 # server 127.0.0.1:10003;
14 # server 127.0.0.1:5002;
14 # server 127.0.0.1:10004;
15 }
15 }
16
16
17 ## gist alias
17 ## gist alias
18
18
19 server {
19 server {
20 listen 443;
20 listen 443;
21 server_name gist.myserver.com;
21 server_name gist.myserver.com;
22 access_log /var/log/nginx/gist.access.log;
22 access_log /var/log/nginx/gist.access.log;
23 error_log /var/log/nginx/gist.error.log;
23 error_log /var/log/nginx/gist.error.log;
24
24
25 ssl on;
25 ssl on;
26 ssl_certificate gist.rhodecode.myserver.com.crt;
26 ssl_certificate gist.rhodecode.myserver.com.crt;
27 ssl_certificate_key gist.rhodecode.myserver.com.key;
27 ssl_certificate_key gist.rhodecode.myserver.com.key;
28
28
29 ssl_session_timeout 5m;
29 ssl_session_timeout 5m;
30
30
31 ssl_protocols SSLv3 TLSv1;
31 ssl_protocols SSLv3 TLSv1;
32 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
32 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
33 ssl_prefer_server_ciphers on;
33 ssl_prefer_server_ciphers on;
34 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
34 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
35
35
36 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
36 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
37 ssl_dhparam /etc/nginx/ssl/dhparam.pem;
37 ssl_dhparam /etc/nginx/ssl/dhparam.pem;
38
38
39 rewrite ^/(.+)$ https://rhodecode.myserver.com/_admin/gists/$1;
39 rewrite ^/(.+)$ https://rhodecode.myserver.com/_admin/gists/$1;
40 rewrite (.*) https://rhodecode.myserver.com/_admin/gists;
40 rewrite (.*) https://rhodecode.myserver.com/_admin/gists;
41 }
41 }
42
42
43 server {
43 server {
44 listen 443;
44 listen 443;
45 server_name rhodecode.myserver.com;
45 server_name rhodecode.myserver.com;
46 access_log /var/log/nginx/rhodecode.access.log;
46 access_log /var/log/nginx/rhodecode.access.log;
47 error_log /var/log/nginx/rhodecode.error.log;
47 error_log /var/log/nginx/rhodecode.error.log;
48
48
49 ssl on;
49 ssl on;
50 ssl_certificate rhodecode.myserver.com.crt;
50 ssl_certificate rhodecode.myserver.com.crt;
51 ssl_certificate_key rhodecode.myserver.com.key;
51 ssl_certificate_key rhodecode.myserver.com.key;
52
52
53 ssl_session_timeout 5m;
53 ssl_session_timeout 5m;
54
54
55 ssl_protocols SSLv3 TLSv1;
55 ssl_protocols SSLv3 TLSv1;
56 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
56 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
57 ssl_prefer_server_ciphers on;
57 ssl_prefer_server_ciphers on;
58
58
59 ## uncomment root directive if you want to serve static files by nginx
59 ## uncomment root directive if you want to serve static files by nginx
60 ## requires static_files = false in .ini file
60 ## requires static_files = false in .ini file
61 # root /path/to/installation/rhodecode/public;
61 # root /path/to/rhodecode/installation/public;
62
62
63 include /etc/nginx/proxy.conf;
63 include /etc/nginx/proxy.conf;
64 location / {
64
65 try_files $uri @rhode;
65 location / {
66 }
66 try_files $uri @rhode;
67 }
67
68
68 location @rhode {
69 location @rhode {
69 proxy_pass http://rc;
70 proxy_pass http://rc;
70 }
71 }
71 }
72 }
@@ -1,163 +1,171 b''
1 .. _system-overview-ref:
1 .. _system-overview-ref:
2
2
3 System Overview
3 System Overview
4 ===============
4 ===============
5
5
6 Latest Version
6 Latest Version
7 --------------
7 --------------
8
8
9 * |release| on Unix and Windows systems.
9 * |release| on Unix and Windows systems.
10
10
11 System Architecture
11 System Architecture
12 -------------------
12 -------------------
13
13
14 The following diagram shows a typical production architecture.
14 The following diagram shows a typical production architecture.
15
15
16 .. image:: ../images/architecture-diagram.png
16 .. image:: ../images/architecture-diagram.png
17 :align: center
17 :align: center
18
18
19 Supported Operating Systems
19 Supported Operating Systems
20 ---------------------------
20 ---------------------------
21
21
22 Linux
22 Linux
23 ^^^^^
23 ^^^^^
24
24
25 * Ubuntu 14.04
25 * Ubuntu 14.04
26 * CentOS 6.2 and 7
26 * CentOS 6.2 and 7
27 * Debian 7.8
27 * Debian 7.8
28 * RedHat Fedora
28 * RedHat Fedora
29 * Arch Linux
29 * Arch Linux
30 * SUSE Linux
30 * SUSE Linux
31
31
32 Windows
32 Windows
33 ^^^^^^^
33 ^^^^^^^
34
34
35 * Windows Vista Ultimate 64bit
35 * Windows Vista Ultimate 64bit
36 * Windows 7 Ultimate 64bit
36 * Windows 7 Ultimate 64bit
37 * Windows 8 Professional 64bit
37 * Windows 8 Professional 64bit
38 * Windows 8.1 Enterprise 64bit
38 * Windows 8.1 Enterprise 64bit
39 * Windows Server 2008 64bit
39 * Windows Server 2008 64bit
40 * Windows Server 2008-R2 64bit
40 * Windows Server 2008-R2 64bit
41 * Windows Server 2012 64bit
41 * Windows Server 2012 64bit
42
42
43 Supported Databases
43 Supported Databases
44 -------------------
44 -------------------
45
45
46 * SQLite
46 * SQLite
47 * MySQL
47 * MySQL
48 * MariaDB
48 * MariaDB
49 * PostgreSQL
49 * PostgreSQL
50
50
51 Supported Browsers
51 Supported Browsers
52 ------------------
52 ------------------
53
53
54 * Chrome
54 * Chrome
55 * Safari
55 * Safari
56 * Firefox
56 * Firefox
57 * Internet Explorer 10 & 11
57 * Internet Explorer 10 & 11
58
58
59 System Requirements
59 System Requirements
60 -------------------
60 -------------------
61
61
62 |RCM| performs best on machines with ultra-fast hard disks. Generally disk
62 |RCM| performs best on machines with ultra-fast hard disks. Generally disk
63 performance is more important than CPU performance. In a corporate production
63 performance is more important than CPU performance. In a corporate production
64 environment handling 1000s of users and |repos| you should deploy on a 12+
64 environment handling 1000s of users and |repos| you should deploy on a 12+
65 core 64GB RAM server. In short, the more RAM the better.
65 core 64GB RAM server. In short, the more RAM the better.
66
66
67
68 For example:
69
70 - for team of 1 - 5 active users you can run on 1GB RAM machine with 1CPU
71 - above 250 active users, |RCM| needs at least 8GB of memory.
72 Number of CPUs is less important, but recommended to have at least 2-3 CPUs
73
74
67 .. _config-rce-files:
75 .. _config-rce-files:
68
76
69 Configuration Files
77 Configuration Files
70 -------------------
78 -------------------
71
79
72 * :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
80 * :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
73 * :file:`/home/{user}/.rccontrol/{instance-id}/mapping.ini`
81 * :file:`/home/{user}/.rccontrol/{instance-id}/mapping.ini`
74 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
82 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
75 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.ini`
83 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.ini`
76 * :file:`/home/{user}/.rccontrol.ini`
84 * :file:`/home/{user}/.rccontrol.ini`
77 * :file:`/home/{user}/.rhoderc`
85 * :file:`/home/{user}/.rhoderc`
78 * :file:`/home/{user}/.rccontrol/cache/MANIFEST`
86 * :file:`/home/{user}/.rccontrol/cache/MANIFEST`
79
87
80 For more information, see the :ref:`config-files` section.
88 For more information, see the :ref:`config-files` section.
81
89
82 Log Files
90 Log Files
83 ---------
91 ---------
84
92
85 * :file:`/home/{user}/.rccontrol/{instance-id}/enterprise.log`
93 * :file:`/home/{user}/.rccontrol/{instance-id}/enterprise.log`
86 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.log`
94 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.log`
87 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.log`
95 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.log`
88 * :file:`/tmp/rccontrol.log`
96 * :file:`/tmp/rccontrol.log`
89 * :file:`/tmp/rhodecode_tools.log`
97 * :file:`/tmp/rhodecode_tools.log`
90
98
91 Storage Files
99 Storage Files
92 -------------
100 -------------
93
101
94 * :file:`/home/{user}/.rccontrol/{instance-id}/data/index/{index-file.toc}`
102 * :file:`/home/{user}/.rccontrol/{instance-id}/data/index/{index-file.toc}`
95 * :file:`/home/{user}/repos/.rc_gist_store`
103 * :file:`/home/{user}/repos/.rc_gist_store`
96 * :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.db`
104 * :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.db`
97 * :file:`/opt/rhodecode/store/{unique-hash}`
105 * :file:`/opt/rhodecode/store/{unique-hash}`
98
106
99 Default Repositories Location
107 Default Repositories Location
100 -----------------------------
108 -----------------------------
101
109
102 * :file:`/home/{user}/repos`
110 * :file:`/home/{user}/repos`
103
111
104 Connection Methods
112 Connection Methods
105 ------------------
113 ------------------
106
114
107 * HTTPS
115 * HTTPS
108 * SSH
116 * SSH
109 * |RCM| API
117 * |RCM| API
110
118
111 Internationalization Support
119 Internationalization Support
112 ----------------------------
120 ----------------------------
113
121
114 Currently available in the following languages, see `Transifex`_ for the
122 Currently available in the following languages, see `Transifex`_ for the
115 latest details. If you want a new language added, please contact us. To
123 latest details. If you want a new language added, please contact us. To
116 configure your language settings, see the :ref:`set-lang` section.
124 configure your language settings, see the :ref:`set-lang` section.
117
125
118 .. hlist::
126 .. hlist::
119
127
120 * Belorussian
128 * Belorussian
121 * Chinese
129 * Chinese
122 * French
130 * French
123 * German
131 * German
124 * Italian
132 * Italian
125 * Japanese
133 * Japanese
126 * Portuguese
134 * Portuguese
127 * Polish
135 * Polish
128 * Russian
136 * Russian
129 * Spanish
137 * Spanish
130
138
131 Licencing Information
139 Licencing Information
132 ---------------------
140 ---------------------
133
141
134 * See licencing information `here`_
142 * See licencing information `here`_
135
143
136 Peer-to-peer Failover Support
144 Peer-to-peer Failover Support
137 -----------------------------
145 -----------------------------
138
146
139 * Yes
147 * Yes
140
148
141 Additional Binaries
149 Additional Binaries
142 -------------------
150 -------------------
143
151
144 * Yes, see :ref:`rhodecode-nix-ref` for full details.
152 * Yes, see :ref:`rhodecode-nix-ref` for full details.
145
153
146 Remote Connectivity
154 Remote Connectivity
147 -------------------
155 -------------------
148
156
149 * Available
157 * Available
150
158
151 Executable Files
159 Executable Files
152 ----------------
160 ----------------
153
161
154 Windows: :file:`RhodeCode-installer-{version}.exe`
162 Windows: :file:`RhodeCode-installer-{version}.exe`
155
163
156 Deprecated Support
164 Deprecated Support
157 ------------------
165 ------------------
158
166
159 - Internet Explorer 8 support deprecated since version 3.7.0.
167 - Internet Explorer 8 support deprecated since version 3.7.0.
160 - Internet Explorer 9 support deprecated since version 3.8.0.
168 - Internet Explorer 9 support deprecated since version 3.8.0.
161
169
162 .. _here: https://rhodecode.com/licenses/
170 .. _here: https://rhodecode.com/licenses/
163 .. _Transifex: https://www.transifex.com/projects/p/RhodeCode/
171 .. _Transifex: https://www.transifex.com/projects/p/RhodeCode/
@@ -1,32 +1,34 b''
1 # Try and keep this list alphabetical
1 # Try and keep this list alphabetical
2 # ui is for user interface elements and messages
2 # ui is for user interface elements and messages
3 # button - that's obvious
3 # button - that's obvious
4
4
5 rst_epilog = '''
5 rst_epilog = '''
6 .. |AE| replace:: Appenlight
6 .. |AE| replace:: Appenlight
7 .. |authtoken| replace:: Authentication Token
7 .. |authtoken| replace:: Authentication Token
8 .. |authtokens| replace:: **Auth Tokens**
8 .. |authtokens| replace:: **Auth Tokens**
9 .. |git| replace:: Git
9 .. |git| replace:: Git
10 .. |hg| replace:: Mercurial
10 .. |hg| replace:: Mercurial
11 .. |svn| replace:: Subversion
11 .. |svn| replace:: Subversion
12 .. |LDAP| replace:: LDAP / Active Directory
12 .. |LDAP| replace:: LDAP / Active Directory
13 .. |os| replace:: operating system
13 .. |os| replace:: operating system
14 .. |OS| replace:: Operating System
14 .. |OS| replace:: Operating System
15 .. |PY| replace:: Python
15 .. |PY| replace:: Python
16 .. |pr| replace:: pull request
16 .. |pr| replace:: pull request
17 .. |prs| replace:: pull requests
17 .. |prs| replace:: pull requests
18 .. |psf| replace:: Python Software Foundation
18 .. |psf| replace:: Python Software Foundation
19 .. |repo| replace:: repository
19 .. |repo| replace:: repository
20 .. |repos| replace:: repositories
20 .. |repos| replace:: repositories
21 .. |RCI| replace:: RhodeCode Control
21 .. |RCI| replace:: RhodeCode Control
22 .. |RCC| replace:: RhodeCode Control
22 .. |RCC| replace:: RhodeCode Control
23 .. |RCV| replace:: RhodeCode Enterprise
23 .. |RCV| replace:: RhodeCode Enterprise
24 .. |RCM| replace:: RhodeCode Enterprise
24 .. |RCM| replace:: RhodeCode Enterprise
25 .. |RCE| replace:: RhodeCode Enterprise
25 .. |RCE| replace:: RhodeCode Enterprise
26 .. |RCCE| replace:: RhodeCode Community
27 .. |RCEE| replace:: RhodeCode Enterprise
26 .. |RCX| replace:: RhodeCode Extensions
28 .. |RCX| replace:: RhodeCode Extensions
27 .. |RCT| replace:: RhodeCode Tools
29 .. |RCT| replace:: RhodeCode Tools
28 .. |RCEBOLD| replace:: **RhodeCode Enterprise**
30 .. |RCEBOLD| replace:: **RhodeCode Enterprise**
29 .. |RCEITALICS| replace:: `RhodeCode Enterprise`
31 .. |RCEITALICS| replace:: `RhodeCode Enterprise`
30 .. |RC| replace:: RhodeCode
32 .. |RC| replace:: RhodeCode
31 .. |RNS| replace:: Release Notes
33 .. |RNS| replace:: Release Notes
32 '''
34 '''
@@ -1,38 +1,38 b''
1 .. _config-database:
1 .. _config-database:
2
2
3 Make Database Changes
3 Make Database Changes
4 ---------------------
4 ---------------------
5
5
6 .. important::
6 .. important::
7
7
8 If you do change the |repo| database that |RCM| uses, then you will need to
8 If you do change the |repo| database that |RCEE| uses, then you will need to
9 upgrade the database, and also remap and rescan the |repos|. More detailed
9 upgrade the database, and also remap and rescan the |repos|. More detailed
10 information is available in the
10 information is available in the
11 :ref:`Alternative upgrade documentation <control:install-port>`.
11 :ref:`Alternative upgrade documentation <control:install-port>`.
12
12
13 If you need to change database connection details for a |RCM| instance,
13 If you need to change database connection details for a |RCEE| instance,
14 use the following steps:
14 use the following steps:
15
15
16 1. Open the :file:`rhodecode.ini` file for the instance you wish to edit. The
16 1. Open the :file:`rhodecode.ini` file for the instance you wish to edit. The
17 default location is
17 default location is
18 :file:`home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
18 :file:`home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
19 2. When you open the file, find the database configuration section,
19 2. When you open the file, find the database configuration section,
20 and use the below example to change the
20 and use the below example to change the
21 connection details:
21 connection details:
22
22
23 .. code-block:: ini
23 .. code-block:: ini
24
24
25 #########################################################
25 #########################################################
26 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
26 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
27 #########################################################
27 #########################################################
28
28
29 # Default SQLite config
29 # Default SQLite config
30 sqlalchemy.db1.url = sqlite:////home/brian/.rccontrol/enterprise-1/rhodecode.db
30 sqlalchemy.db1.url = sqlite:////home/brian/.rccontrol/enterprise-1/rhodecode.db
31
31
32 # Use this example for a PostgreSQL
32 # Use this example for a PostgreSQL
33 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
33 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
34
34
35 # see sqlalchemy docs for other advanced settings
35 # see sqlalchemy docs for other advanced settings
36 sqlalchemy.db1.echo = false
36 sqlalchemy.db1.echo = false
37 sqlalchemy.db1.pool_recycle = 3600
37 sqlalchemy.db1.pool_recycle = 3600
38 sqlalchemy.db1.convert_unicode = true
38 sqlalchemy.db1.convert_unicode = true
@@ -1,102 +1,109 b''
1 .. _quick-start:
1 .. _quick-start:
2
2
3 Quick Start Guide
3 Quick Start Guide
4 =================
4 =================
5
5
6 .. important::
6 .. important::
7
7
8 These are quick start instructions. To optimize your |RCE|,
8 These are quick start instructions. To optimize your |RCE|,
9 |RCC|, and |RCT| usage, read the more detailed instructions in our guides.
9 |RCC|, and |RCT| usage, read the more detailed instructions in our guides.
10 For detailed installation instructions, see
10 For detailed installation instructions, see
11 :ref:`RhodeCode Control Documentation <control:rcc>`
11 :ref:`RhodeCode Control Documentation <control:rcc>`
12
12
13 .. tip::
13 .. tip::
14
14
15 If using a non-SQLite database, install and configure the database, create
15 If using a non-SQLite database, install and configure the database, create
16 a new user, and grant permissions. You will be prompted for this user's
16 a new user, and grant permissions. You will be prompted for this user's
17 credentials during |RCE| installation. See the relevant database
17 credentials during |RCE| installation. See the relevant database
18 documentation for more details.
18 documentation for more details.
19
19
20 To get |RCM| up and running, run through the below steps:
20 To get |RCE| up and running, run through the below steps:
21
21
22 1. Download the latest |RCC| installer from your `rhodecode.com`_ profile
22 1. Download the latest |RCC| installer from your `rhodecode.com`_ profile
23 page. If you don't have an account, sign up at `rhodecode.com/register`_.
23 or main page.
24 If you don't have an account, sign up at `rhodecode.com/register`_.
25
24 2. Run the |RCC| installer and accept the End User Licence using the
26 2. Run the |RCC| installer and accept the End User Licence using the
25 following example:
27 following example:
26
28
27 .. code-block:: bash
29 .. code-block:: bash
28
30
29 $ chmod 755 RhodeCode-installer-linux-*
31 $ chmod 755 RhodeCode-installer-linux-*
30 $ ./RhodeCode-installer-linux-*
32 $ ./RhodeCode-installer-linux-*
31
33
32 3. Install a VCS Server, and configure it to start at boot.
34 3. Install a VCS Server, and configure it to start at boot.
33
35
34 .. code-block:: bash
36 .. code-block:: bash
35
37
36 $ rccontrol install VCSServer
38 $ rccontrol install VCSServer
37
39
38 Agree to the licence agreement? [y/N]: y
40 Agree to the licence agreement? [y/N]: y
39 IP to start the server on [127.0.0.1]:
41 IP to start the server on [127.0.0.1]:
40 Port for the server to start [10005]:
42 Port for the server to start [10005]:
41 Creating new instance: vcsserver-1
43 Creating new instance: vcsserver-1
42 Installing RhodeCode VCSServer
44 Installing RhodeCode VCSServer
43 Configuring RhodeCode VCS Server ...
45 Configuring RhodeCode VCS Server ...
44 Supervisord state is: RUNNING
46 Supervisord state is: RUNNING
45 Added process group vcsserver-1
47 Added process group vcsserver-1
46
48
47
49
48 4. Install |RCE|. If using MySQL or PostgreSQL, during installation you'll be
50 4. Install |RCEE| or |RCCE|. If using MySQL or PostgreSQL, during
49 asked for your database credentials, so have them at hand. You don't need
51 installation you'll be asked for your database credentials, so have them at hand.
50 any for SQLite.
52 Mysql or Postgres needs to be running and a new database needs to be created.
53 You don't need any credentials or to create a database for SQLite.
51
54
52 .. code-block:: bash
55 .. code-block:: bash
53 :emphasize-lines: 11-16
56 :emphasize-lines: 11-16
54
57
58 $ rccontrol install Community
59
60 or
61
55 $ rccontrol install Enterprise
62 $ rccontrol install Enterprise
56
63
57 Username [admin]: username
64 Username [admin]: username
58 Password (min 6 chars):
65 Password (min 6 chars):
59 Repeat for confirmation:
66 Repeat for confirmation:
60 Email: your@mail.com
67 Email: your@mail.com
61 Respositories location [/home/brian/repos]:
68 Respositories location [/home/brian/repos]:
62 IP to start the Enterprise server on [127.0.0.1]:
69 IP to start the Enterprise server on [127.0.0.1]:
63 Port for the Enterprise server to use [10004]:
70 Port for the Enterprise server to use [10004]:
64 Database type - [s]qlite, [m]ysql, [p]ostresql:
71 Database type - [s]qlite, [m]ysql, [p]ostresql:
65 PostgreSQL selected
72 PostgreSQL selected
66 Database host [127.0.0.1]:
73 Database host [127.0.0.1]:
67 Database port [5432]:
74 Database port [5432]:
68 Database username: db-user-name
75 Database username: db-user-name
69 Database password: somepassword
76 Database password: somepassword
70 Database name: example-db-name
77 Database name: example-db-name
71
78
72 5. Check the status of your installation. You |RCE| instance runs on the URL
79 5. Check the status of your installation. You |RCEE|/|RCCE| instance runs
73 displayed in the status message.
80 on the URL displayed in the status message.
74
81
75 .. code-block:: bash
82 .. code-block:: bash
76
83
77 $ rccontrol status
84 $ rccontrol status
78
85
79 - NAME: enterprise-1
86 - NAME: enterprise-1
80 - STATUS: RUNNING
87 - STATUS: RUNNING
81 - TYPE: Enterprise
88 - TYPE: Enterprise
82 - VERSION: 3.3.0
89 - VERSION: 4.1.0
83 - URL: http://127.0.0.1:10003
90 - URL: http://127.0.0.1:10003
84
91
85 - NAME: vcsserver-1
92 - NAME: vcsserver-1
86 - STATUS: RUNNING
93 - STATUS: RUNNING
87 - TYPE: VCSServer
94 - TYPE: VCSServer
88 - VERSION: 3.3.0
95 - VERSION: 4.1.0
89 - URL: http://127.0.0.1:10001
96 - URL: http://127.0.0.1:10001
90
97
91 .. note::
98 .. note::
92
99
93 Recommended post quick start install instructions:
100 Recommended post quick start install instructions:
94
101
95 * Read the documentation
102 * Read the documentation
96 * Carry out the :ref:`rhodecode-post-instal-ref`
103 * Carry out the :ref:`rhodecode-post-instal-ref`
97 * Set up :ref:`indexing-ref`
104 * Set up :ref:`indexing-ref`
98 * Familiarise yourself with the :ref:`rhodecode-admin-ref` section.
105 * Familiarise yourself with the :ref:`rhodecode-admin-ref` section.
99
106
100 .. _rhodecode.com/download/: https://rhodecode.com/download/
107 .. _rhodecode.com/download/: https://rhodecode.com/download/
101 .. _rhodecode.com: https://rhodecode.com/
108 .. _rhodecode.com: https://rhodecode.com/
102 .. _rhodecode.com/register: https://rhodecode.com/register/
109 .. _rhodecode.com/register: https://rhodecode.com/register/
@@ -1,58 +1,64 b''
1 |RCE| 4.0.0 |RNS|
1 |RCE| 4.0.0 |RNS|
2 -----------------
2 -----------------
3
3
4 Release Date
4 Release Date
5 ^^^^^^^^^^^^
5 ^^^^^^^^^^^^
6
6
7 - 2016-05-24
7 - 2016-05-24
8
8
9 General
9 General
10 ^^^^^^^
10 ^^^^^^^
11
11
12 - Introduced Pyramid as a Pylons framework replacement. (porting still ongoing).
12 - Introduced Pyramid as a Pylons framework replacement. (porting still ongoing).
13 Added few of components as plugins. Exposed RhodeCode plugins API for 3rd
13 Added few of components as plugins. Exposed RhodeCode plugins API for 3rd
14 parties to extend RhodeCode functionality with custom pyramid apps. Pyramid
14 parties to extend RhodeCode functionality with custom pyramid apps. Pyramid
15 is also our route to python3 support.
15 is also our route to python3 support.
16 - Various UX/UI improvements.
16 - Various UX/UI improvements.
17 - new summary page
17 - new summary page
18 - new file browser (more consistent)
18 - new file browser (more consistent)
19 - re-done admin section and added Panels
19 - re-done admin section and added Panels
20 - various other tweaks and improvements
20 - various other tweaks and improvements
21 - Alternative fast and scalable HTTP based communication backend for VCServer.
21 - Alternative fast and scalable HTTP based communication backend for VCServer.
22 It soon will replace Pyro4.
22 It soon will replace Pyro4.
23 - Rewrote few caching techniques used and simplified those
23 - Rewrote few caching techniques used and simplified those
24
24
25
25
26 New Features
26 New Features
27 ^^^^^^^^^^^^
27 ^^^^^^^^^^^^
28
28
29 - RhodeCode code-review live chat (EE only). A live communication
29 - RhodeCode code-review live chat (EE only). A live communication
30 tool built right into the code-review process to quickly
30 tool built right into the code-review process to quickly
31 collaborate on crucial parts of code.
31 collaborate on crucial parts of code.
32
32
33 - Elastic Search backend (EE only). Alternative backend to existing
33 - Elastic Search backend (EE only). Alternative backend to existing
34 Whoosh to handle, large amount of data for full text search.
34 Whoosh to handle, large amount of data for full text search.
35
35
36 - Social Auth (EE only): added new social authentication backends including:
36 - Social Auth (EE only): added new social authentication backends including:
37 Github, Twitter, Bitbucket and Google. It's possible now to use your
37 Github, Twitter, Bitbucket and Google. It's possible now to use your
38 Google account to log in to RhodeCode and take advantage of things like 2FA.
38 Google account to log in to RhodeCode and take advantage of things like 2FA.
39
39
40 - Search: full text search now properly orders commits by date, and shows line
41 numbers for file content search.
42
43
40 Security
44 Security
41 ^^^^^^^^
45 ^^^^^^^^
42
46
43 - Added new action loggers for actions like adding/revoking permissions.
47 - Added new action loggers for actions like adding/revoking permissions.
44
48
45
49
46 Performance
50 Performance
47 ^^^^^^^^^^^
51 ^^^^^^^^^^^
48
52
49 - Optimized admin pannels to faster load large ammount of data
53 - Optimized admin panels to faster load large amount of data
50 - Improved file tree loading speed
54 - Improved file tree loading speed
55 - New HTTP backend is ~10% faster, and doesn't require so many threads
56 for vcsserver
51
57
52
58
53 Fixes
59 Fixes
54 ^^^^^
60 ^^^^^
55
61
56 - Fixed backreferences to user group when deleting users
62 - Fixed backreferences to user group when deleting users
57 - Fixed LDAP group user-group matching
63 - Fixed LDAP group user-group matching
58 - Improved SVN support for various commands (MKOL, etc) No newline at end of file
64 - Improved SVN support for various commands (MKOL, etc)
@@ -1,76 +1,80 b''
1 .. _rhodecode-release-notes-ref:
1 .. _rhodecode-release-notes-ref:
2
2
3 Release Notes
3 Release Notes
4 =============
4 =============
5
5
6 |RCE| 4.x Versions
6 |RCE| 4.x Versions
7 ------------------
7 ------------------
8
8
9 .. toctree::
10 :maxdepth: 1
11
12 release-notes-4.1.0.rst
9 release-notes-4.0.1.rst
13 release-notes-4.0.1.rst
10 release-notes-4.0.0.rst
14 release-notes-4.0.0.rst
11
15
12 |RCE| 3.x Versions
16 |RCE| 3.x Versions
13 ------------------
17 ------------------
14
18
15 .. toctree::
19 .. toctree::
16 :maxdepth: 1
20 :maxdepth: 1
17
21
18 release-notes-3.8.4.rst
22 release-notes-3.8.4.rst
19 release-notes-3.8.3.rst
23 release-notes-3.8.3.rst
20 release-notes-3.8.2.rst
24 release-notes-3.8.2.rst
21 release-notes-3.8.1.rst
25 release-notes-3.8.1.rst
22 release-notes-3.8.0.rst
26 release-notes-3.8.0.rst
23 release-notes-3.7.1.rst
27 release-notes-3.7.1.rst
24 release-notes-3.7.0.rst
28 release-notes-3.7.0.rst
25 release-notes-3.6.1.rst
29 release-notes-3.6.1.rst
26 release-notes-3.6.0.rst
30 release-notes-3.6.0.rst
27 release-notes-3.5.2.rst
31 release-notes-3.5.2.rst
28 release-notes-3.5.1.rst
32 release-notes-3.5.1.rst
29 release-notes-3.5.0.rst
33 release-notes-3.5.0.rst
30 release-notes-3.4.1.rst
34 release-notes-3.4.1.rst
31 release-notes-3.4.0.rst
35 release-notes-3.4.0.rst
32 release-notes-3.3.4.rst
36 release-notes-3.3.4.rst
33 release-notes-3.3.3.rst
37 release-notes-3.3.3.rst
34 release-notes-3.3.2.rst
38 release-notes-3.3.2.rst
35 release-notes-3.3.1.rst
39 release-notes-3.3.1.rst
36 release-notes-3.3.0.rst
40 release-notes-3.3.0.rst
37 release-notes-3.2.3.rst
41 release-notes-3.2.3.rst
38 release-notes-3.2.2.rst
42 release-notes-3.2.2.rst
39 release-notes-3.2.1.rst
43 release-notes-3.2.1.rst
40 release-notes-3.2.0.rst
44 release-notes-3.2.0.rst
41 release-notes-3.1.1.rst
45 release-notes-3.1.1.rst
42 release-notes-3.1.0.rst
46 release-notes-3.1.0.rst
43 release-notes-3.0.2.rst
47 release-notes-3.0.2.rst
44 release-notes-3.0.1.rst
48 release-notes-3.0.1.rst
45 release-notes-3.0.0.rst
49 release-notes-3.0.0.rst
46
50
47 |RCE| 2.x Versions
51 |RCE| 2.x Versions
48 ------------------
52 ------------------
49
53
50 .. toctree::
54 .. toctree::
51 :maxdepth: 1
55 :maxdepth: 1
52
56
53 release-notes-2.2.8.rst
57 release-notes-2.2.8.rst
54 release-notes-2.2.7.rst
58 release-notes-2.2.7.rst
55 release-notes-2.2.6.rst
59 release-notes-2.2.6.rst
56 release-notes-2.2.5.rst
60 release-notes-2.2.5.rst
57 release-notes-2.2.4.rst
61 release-notes-2.2.4.rst
58 release-notes-2.2.3.rst
62 release-notes-2.2.3.rst
59 release-notes-2.2.2.rst
63 release-notes-2.2.2.rst
60 release-notes-2.2.1.rst
64 release-notes-2.2.1.rst
61 release-notes-2.2.0.rst
65 release-notes-2.2.0.rst
62 release-notes-2.1.0.rst
66 release-notes-2.1.0.rst
63 release-notes-2.0.2.rst
67 release-notes-2.0.2.rst
64 release-notes-2.0.1.rst
68 release-notes-2.0.1.rst
65 release-notes-2.0.0.rst
69 release-notes-2.0.0.rst
66
70
67 |RCE| 1.x Versions
71 |RCE| 1.x Versions
68 ------------------
72 ------------------
69
73
70 .. toctree::
74 .. toctree::
71 :maxdepth: 1
75 :maxdepth: 1
72
76
73 release-notes-1.7.2.rst
77 release-notes-1.7.2.rst
74 release-notes-1.7.1.rst
78 release-notes-1.7.1.rst
75 release-notes-1.7.0.rst
79 release-notes-1.7.0.rst
76 release-notes-1.6.0.rst
80 release-notes-1.6.0.rst
@@ -1,13 +1,12 b''
1 diff --git a/requirements.txt b/requirements.txt
1 diff --git a/requirements.txt b/requirements.txt
2 --- a/requirements.txt
2 --- a/requirements.txt
3 +++ b/requirements.txt
3 +++ b/requirements.txt
4 @@ -1,8 +1,8 @@
4 @@ -3,7 +3,7 @@future==0.14.3
5 click==5.1
6 future==0.14.3
7 six==1.9.0
5 six==1.9.0
8 mako==1.0.1
6 mako==1.0.1
9 markupsafe==0.23
7 markupsafe==0.23
10 -requests==2.5.1
8 -requests==2.5.1
11 +requests
9 +requests
10 #responses
12 whoosh==2.7.0
11 whoosh==2.7.0
13 pyelasticsearch==1.4
12 elasticsearch==2.3.0 No newline at end of file
@@ -1,151 +1,165 b''
1 # Overrides for the generated python-packages.nix
1 # Overrides for the generated python-packages.nix
2 #
2 #
3 # This function is intended to be used as an extension to the generated file
3 # This function is intended to be used as an extension to the generated file
4 # python-packages.nix. The main objective is to add needed dependencies of C
4 # python-packages.nix. The main objective is to add needed dependencies of C
5 # libraries and tweak the build instructions where needed.
5 # libraries and tweak the build instructions where needed.
6
6
7 { pkgs, basePythonPackages }:
7 { pkgs, basePythonPackages }:
8
8
9 let
9 let
10 sed = "sed -i";
10 sed = "sed -i";
11 in
11 in
12
12
13 self: super: {
13 self: super: {
14
14
15 gnureadline = super.gnureadline.override (attrs: {
15 gnureadline = super.gnureadline.override (attrs: {
16 buildInputs = attrs.buildInputs ++ [
16 buildInputs = attrs.buildInputs ++ [
17 pkgs.ncurses
17 pkgs.ncurses
18 ];
18 ];
19 patchPhase = ''
19 patchPhase = ''
20 substituteInPlace setup.py --replace "/bin/bash" "${pkgs.bash}/bin/bash"
20 substituteInPlace setup.py --replace "/bin/bash" "${pkgs.bash}/bin/bash"
21 '';
21 '';
22 });
22 });
23
23
24 gunicorn = super.gunicorn.override (attrs: {
25 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
26 # johbo: futures is needed as long as we are on Python 2, otherwise
27 # gunicorn explodes if used with multiple threads per worker.
28 self.futures
29 ];
30 });
31
32 ipython = super.ipython.override (attrs: {
33 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
34 self.gnureadline
35 ];
36 });
37
24 kombu = super.kombu.override (attrs: {
38 kombu = super.kombu.override (attrs: {
25 # The current version of kombu needs some patching to work with the
39 # The current version of kombu needs some patching to work with the
26 # other libs. Should be removed once we update celery and kombu.
40 # other libs. Should be removed once we update celery and kombu.
27 patches = [
41 patches = [
28 ./patch-kombu-py-2-7-11.diff
42 ./patch-kombu-py-2-7-11.diff
29 ./patch-kombu-msgpack.diff
43 ./patch-kombu-msgpack.diff
30 ];
44 ];
31 });
45 });
32
46
33 lxml = super.lxml.override (attrs: {
47 lxml = super.lxml.override (attrs: {
34 buildInputs = with self; [
48 buildInputs = with self; [
35 pkgs.libxml2
49 pkgs.libxml2
36 pkgs.libxslt
50 pkgs.libxslt
37 ];
51 ];
38 });
52 });
39
53
40 MySQL-python = super.MySQL-python.override (attrs: {
54 MySQL-python = super.MySQL-python.override (attrs: {
41 buildInputs = attrs.buildInputs ++ [
55 buildInputs = attrs.buildInputs ++ [
42 pkgs.openssl
56 pkgs.openssl
43 ];
57 ];
44 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
58 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
45 pkgs.mysql.lib
59 pkgs.mysql.lib
46 pkgs.zlib
60 pkgs.zlib
47 ];
61 ];
48 });
62 });
49
63
50 psutil = super.psutil.override (attrs: {
64 psutil = super.psutil.override (attrs: {
51 buildInputs = attrs.buildInputs ++
65 buildInputs = attrs.buildInputs ++
52 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.darwin.IOKit;
66 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.darwin.IOKit;
53 });
67 });
54
68
55 psycopg2 = super.psycopg2.override (attrs: {
69 psycopg2 = super.psycopg2.override (attrs: {
56 buildInputs = attrs.buildInputs ++
70 buildInputs = attrs.buildInputs ++
57 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.openssl;
71 pkgs.lib.optional pkgs.stdenv.isDarwin pkgs.openssl;
58 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
72 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
59 pkgs.postgresql
73 pkgs.postgresql
60 ];
74 ];
61 });
75 });
62
76
63 pycurl = super.pycurl.override (attrs: {
77 pycurl = super.pycurl.override (attrs: {
64 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
78 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
65 pkgs.curl
79 pkgs.curl
66 pkgs.openssl
80 pkgs.openssl
67 ];
81 ];
68 preConfigure = ''
82 preConfigure = ''
69 substituteInPlace setup.py --replace '--static-libs' '--libs'
83 substituteInPlace setup.py --replace '--static-libs' '--libs'
70 export PYCURL_SSL_LIBRARY=openssl
84 export PYCURL_SSL_LIBRARY=openssl
71 '';
85 '';
72 });
86 });
73
87
74 Pylons = super.Pylons.override (attrs: {
88 Pylons = super.Pylons.override (attrs: {
75 name = "Pylons-1.0.1-patch1";
89 name = "Pylons-1.0.1-patch1";
76 src = pkgs.fetchgit {
90 src = pkgs.fetchgit {
77 url = "https://code.rhodecode.com/upstream/pylons";
91 url = "https://code.rhodecode.com/upstream/pylons";
78 rev = "707354ee4261b9c10450404fc9852ccea4fd667d";
92 rev = "707354ee4261b9c10450404fc9852ccea4fd667d";
79 sha256 = "b2763274c2780523a335f83a1df65be22ebe4ff413a7bc9e9288d23c1f62032e";
93 sha256 = "b2763274c2780523a335f83a1df65be22ebe4ff413a7bc9e9288d23c1f62032e";
80 };
94 };
81 });
95 });
82
96
83 pyramid = super.pyramid.override (attrs: {
97 pyramid = super.pyramid.override (attrs: {
84 postFixup = ''
98 postFixup = ''
85 wrapPythonPrograms
99 wrapPythonPrograms
86 # TODO: johbo: "wrapPython" adds this magic line which
100 # TODO: johbo: "wrapPython" adds this magic line which
87 # confuses pserve.
101 # confuses pserve.
88 ${sed} '/import sys; sys.argv/d' $out/bin/.pserve-wrapped
102 ${sed} '/import sys; sys.argv/d' $out/bin/.pserve-wrapped
89 '';
103 '';
90 });
104 });
91
105
92 Pyro4 = super.Pyro4.override (attrs: {
106 Pyro4 = super.Pyro4.override (attrs: {
93 # TODO: Was not able to generate this version, needs further
107 # TODO: Was not able to generate this version, needs further
94 # investigation.
108 # investigation.
95 name = "Pyro4-4.35";
109 name = "Pyro4-4.35";
96 src = pkgs.fetchurl {
110 src = pkgs.fetchurl {
97 url = "https://pypi.python.org/packages/source/P/Pyro4/Pyro4-4.35.src.tar.gz";
111 url = "https://pypi.python.org/packages/source/P/Pyro4/Pyro4-4.35.src.tar.gz";
98 md5 = "cbe6cb855f086a0f092ca075005855f3";
112 md5 = "cbe6cb855f086a0f092ca075005855f3";
99 };
113 };
100 });
114 });
101
115
102 pysqlite = super.pysqlite.override (attrs: {
116 pysqlite = super.pysqlite.override (attrs: {
103 propagatedBuildInputs = [
117 propagatedBuildInputs = [
104 pkgs.sqlite
118 pkgs.sqlite
105 ];
119 ];
106 });
120 });
107
121
108 pytest-runner = super.pytest-runner.override (attrs: {
122 pytest-runner = super.pytest-runner.override (attrs: {
109 propagatedBuildInputs = [
123 propagatedBuildInputs = [
110 self.setuptools-scm
124 self.setuptools-scm
111 ];
125 ];
112 });
126 });
113
127
114 python-ldap = super.python-ldap.override (attrs: {
128 python-ldap = super.python-ldap.override (attrs: {
115 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
129 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
116 pkgs.cyrus_sasl
130 pkgs.cyrus_sasl
117 pkgs.openldap
131 pkgs.openldap
118 pkgs.openssl
132 pkgs.openssl
119 ];
133 ];
120 NIX_CFLAGS_COMPILE = "-I${pkgs.cyrus_sasl}/include/sasl";
134 NIX_CFLAGS_COMPILE = "-I${pkgs.cyrus_sasl}/include/sasl";
121 });
135 });
122
136
123 python-pam = super.python-pam.override (attrs:
137 python-pam = super.python-pam.override (attrs:
124 let
138 let
125 includeLibPam = pkgs.stdenv.isLinux;
139 includeLibPam = pkgs.stdenv.isLinux;
126 in {
140 in {
127 # TODO: johbo: Move the option up into the default.nix, we should
141 # TODO: johbo: Move the option up into the default.nix, we should
128 # include python-pam only on supported platforms.
142 # include python-pam only on supported platforms.
129 propagatedBuildInputs = attrs.propagatedBuildInputs ++
143 propagatedBuildInputs = attrs.propagatedBuildInputs ++
130 pkgs.lib.optional includeLibPam [
144 pkgs.lib.optional includeLibPam [
131 pkgs.pam
145 pkgs.pam
132 ];
146 ];
133 # TODO: johbo: Check if this can be avoided, or transform into
147 # TODO: johbo: Check if this can be avoided, or transform into
134 # a real patch
148 # a real patch
135 patchPhase = pkgs.lib.optionals includeLibPam ''
149 patchPhase = pkgs.lib.optionals includeLibPam ''
136 substituteInPlace pam.py \
150 substituteInPlace pam.py \
137 --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"'
151 --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"'
138 '';
152 '';
139 });
153 });
140
154
141 rhodecode-tools = super.rhodecode-tools.override (attrs: {
155 rhodecode-tools = super.rhodecode-tools.override (attrs: {
142 patches = [
156 patches = [
143 ./patch-rhodecode-tools-setup.diff
157 ./patch-rhodecode-tools-setup.diff
144 ];
158 ];
145 });
159 });
146
160
147 # Avoid that setuptools is replaced, this leads to trouble
161 # Avoid that setuptools is replaced, this leads to trouble
148 # with buildPythonPackage.
162 # with buildPythonPackage.
149 setuptools = basePythonPackages.setuptools;
163 setuptools = basePythonPackages.setuptools;
150
164
151 }
165 }
@@ -1,1273 +1,1263 b''
1 {
1 {
2 Babel = super.buildPythonPackage {
2 Babel = super.buildPythonPackage {
3 name = "Babel-1.3";
3 name = "Babel-1.3";
4 buildInputs = with self; [];
4 buildInputs = with self; [];
5 doCheck = false;
5 doCheck = false;
6 propagatedBuildInputs = with self; [pytz];
6 propagatedBuildInputs = with self; [pytz];
7 src = fetchurl {
7 src = fetchurl {
8 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
8 url = "https://pypi.python.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
9 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
9 md5 = "5264ceb02717843cbc9ffce8e6e06bdb";
10 };
10 };
11 };
11 };
12 Beaker = super.buildPythonPackage {
12 Beaker = super.buildPythonPackage {
13 name = "Beaker-1.7.0";
13 name = "Beaker-1.7.0";
14 buildInputs = with self; [];
14 buildInputs = with self; [];
15 doCheck = false;
15 doCheck = false;
16 propagatedBuildInputs = with self; [];
16 propagatedBuildInputs = with self; [];
17 src = fetchurl {
17 src = fetchurl {
18 url = "https://pypi.python.org/packages/97/8e/409d2e7c009b8aa803dc9e6f239f1db7c3cdf578249087a404e7c27a505d/Beaker-1.7.0.tar.gz";
18 url = "https://pypi.python.org/packages/97/8e/409d2e7c009b8aa803dc9e6f239f1db7c3cdf578249087a404e7c27a505d/Beaker-1.7.0.tar.gz";
19 md5 = "386be3f7fe427358881eee4622b428b3";
19 md5 = "386be3f7fe427358881eee4622b428b3";
20 };
20 };
21 };
21 };
22 CProfileV = super.buildPythonPackage {
22 CProfileV = super.buildPythonPackage {
23 name = "CProfileV-1.0.6";
23 name = "CProfileV-1.0.6";
24 buildInputs = with self; [];
24 buildInputs = with self; [];
25 doCheck = false;
25 doCheck = false;
26 propagatedBuildInputs = with self; [bottle];
26 propagatedBuildInputs = with self; [bottle];
27 src = fetchurl {
27 src = fetchurl {
28 url = "https://pypi.python.org/packages/eb/df/983a0b6cfd3ac94abf023f5011cb04f33613ace196e33f53c86cf91850d5/CProfileV-1.0.6.tar.gz";
28 url = "https://pypi.python.org/packages/eb/df/983a0b6cfd3ac94abf023f5011cb04f33613ace196e33f53c86cf91850d5/CProfileV-1.0.6.tar.gz";
29 md5 = "08c7c242b6e64237bc53c5d13537e03d";
29 md5 = "08c7c242b6e64237bc53c5d13537e03d";
30 };
30 };
31 };
31 };
32 Fabric = super.buildPythonPackage {
32 Fabric = super.buildPythonPackage {
33 name = "Fabric-1.10.0";
33 name = "Fabric-1.10.0";
34 buildInputs = with self; [];
34 buildInputs = with self; [];
35 doCheck = false;
35 doCheck = false;
36 propagatedBuildInputs = with self; [paramiko];
36 propagatedBuildInputs = with self; [paramiko];
37 src = fetchurl {
37 src = fetchurl {
38 url = "https://pypi.python.org/packages/e3/5f/b6ebdb5241d5ec9eab582a5c8a01255c1107da396f849e538801d2fe64a5/Fabric-1.10.0.tar.gz";
38 url = "https://pypi.python.org/packages/e3/5f/b6ebdb5241d5ec9eab582a5c8a01255c1107da396f849e538801d2fe64a5/Fabric-1.10.0.tar.gz";
39 md5 = "2cb96473387f0e7aa035210892352f4a";
39 md5 = "2cb96473387f0e7aa035210892352f4a";
40 };
40 };
41 };
41 };
42 FormEncode = super.buildPythonPackage {
42 FormEncode = super.buildPythonPackage {
43 name = "FormEncode-1.2.4";
43 name = "FormEncode-1.2.4";
44 buildInputs = with self; [];
44 buildInputs = with self; [];
45 doCheck = false;
45 doCheck = false;
46 propagatedBuildInputs = with self; [];
46 propagatedBuildInputs = with self; [];
47 src = fetchurl {
47 src = fetchurl {
48 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
48 url = "https://pypi.python.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
49 md5 = "6bc17fb9aed8aea198975e888e2077f4";
49 md5 = "6bc17fb9aed8aea198975e888e2077f4";
50 };
50 };
51 };
51 };
52 Jinja2 = super.buildPythonPackage {
52 Jinja2 = super.buildPythonPackage {
53 name = "Jinja2-2.7.3";
53 name = "Jinja2-2.7.3";
54 buildInputs = with self; [];
54 buildInputs = with self; [];
55 doCheck = false;
55 doCheck = false;
56 propagatedBuildInputs = with self; [MarkupSafe];
56 propagatedBuildInputs = with self; [MarkupSafe];
57 src = fetchurl {
57 src = fetchurl {
58 url = "https://pypi.python.org/packages/b0/73/eab0bca302d6d6a0b5c402f47ad1760dc9cb2dd14bbc1873ad48db258e4d/Jinja2-2.7.3.tar.gz";
58 url = "https://pypi.python.org/packages/b0/73/eab0bca302d6d6a0b5c402f47ad1760dc9cb2dd14bbc1873ad48db258e4d/Jinja2-2.7.3.tar.gz";
59 md5 = "b9dffd2f3b43d673802fe857c8445b1a";
59 md5 = "b9dffd2f3b43d673802fe857c8445b1a";
60 };
60 };
61 };
61 };
62 Mako = super.buildPythonPackage {
62 Mako = super.buildPythonPackage {
63 name = "Mako-1.0.1";
63 name = "Mako-1.0.1";
64 buildInputs = with self; [];
64 buildInputs = with self; [];
65 doCheck = false;
65 doCheck = false;
66 propagatedBuildInputs = with self; [MarkupSafe];
66 propagatedBuildInputs = with self; [MarkupSafe];
67 src = fetchurl {
67 src = fetchurl {
68 url = "https://pypi.python.org/packages/8e/a4/aa56533ecaa5f22ca92428f74e074d0c9337282933c722391902c8f9e0f8/Mako-1.0.1.tar.gz";
68 url = "https://pypi.python.org/packages/8e/a4/aa56533ecaa5f22ca92428f74e074d0c9337282933c722391902c8f9e0f8/Mako-1.0.1.tar.gz";
69 md5 = "9f0aafd177b039ef67b90ea350497a54";
69 md5 = "9f0aafd177b039ef67b90ea350497a54";
70 };
70 };
71 };
71 };
72 Markdown = super.buildPythonPackage {
72 Markdown = super.buildPythonPackage {
73 name = "Markdown-2.6.2";
73 name = "Markdown-2.6.2";
74 buildInputs = with self; [];
74 buildInputs = with self; [];
75 doCheck = false;
75 doCheck = false;
76 propagatedBuildInputs = with self; [];
76 propagatedBuildInputs = with self; [];
77 src = fetchurl {
77 src = fetchurl {
78 url = "https://pypi.python.org/packages/62/8b/83658b5f6c220d5fcde9f9852d46ea54765d734cfbc5a9f4c05bfc36db4d/Markdown-2.6.2.tar.gz";
78 url = "https://pypi.python.org/packages/62/8b/83658b5f6c220d5fcde9f9852d46ea54765d734cfbc5a9f4c05bfc36db4d/Markdown-2.6.2.tar.gz";
79 md5 = "256d19afcc564dc4ce4c229bb762f7ae";
79 md5 = "256d19afcc564dc4ce4c229bb762f7ae";
80 };
80 };
81 };
81 };
82 MarkupSafe = super.buildPythonPackage {
82 MarkupSafe = super.buildPythonPackage {
83 name = "MarkupSafe-0.23";
83 name = "MarkupSafe-0.23";
84 buildInputs = with self; [];
84 buildInputs = with self; [];
85 doCheck = false;
85 doCheck = false;
86 propagatedBuildInputs = with self; [];
86 propagatedBuildInputs = with self; [];
87 src = fetchurl {
87 src = fetchurl {
88 url = "https://pypi.python.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-0.23.tar.gz";
88 url = "https://pypi.python.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-0.23.tar.gz";
89 md5 = "f5ab3deee4c37cd6a922fb81e730da6e";
89 md5 = "f5ab3deee4c37cd6a922fb81e730da6e";
90 };
90 };
91 };
91 };
92 MySQL-python = super.buildPythonPackage {
92 MySQL-python = super.buildPythonPackage {
93 name = "MySQL-python-1.2.5";
93 name = "MySQL-python-1.2.5";
94 buildInputs = with self; [];
94 buildInputs = with self; [];
95 doCheck = false;
95 doCheck = false;
96 propagatedBuildInputs = with self; [];
96 propagatedBuildInputs = with self; [];
97 src = fetchurl {
97 src = fetchurl {
98 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
98 url = "https://pypi.python.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
99 md5 = "654f75b302db6ed8dc5a898c625e030c";
99 md5 = "654f75b302db6ed8dc5a898c625e030c";
100 };
100 };
101 };
101 };
102 Paste = super.buildPythonPackage {
102 Paste = super.buildPythonPackage {
103 name = "Paste-2.0.2";
103 name = "Paste-2.0.2";
104 buildInputs = with self; [];
104 buildInputs = with self; [];
105 doCheck = false;
105 doCheck = false;
106 propagatedBuildInputs = with self; [six];
106 propagatedBuildInputs = with self; [six];
107 src = fetchurl {
107 src = fetchurl {
108 url = "https://pypi.python.org/packages/d5/8d/0f8ac40687b97ff3e07ebd1369be20bdb3f93864d2dc3c2ff542edb4ce50/Paste-2.0.2.tar.gz";
108 url = "https://pypi.python.org/packages/d5/8d/0f8ac40687b97ff3e07ebd1369be20bdb3f93864d2dc3c2ff542edb4ce50/Paste-2.0.2.tar.gz";
109 md5 = "4bfc8a7eaf858f6309d2ac0f40fc951c";
109 md5 = "4bfc8a7eaf858f6309d2ac0f40fc951c";
110 };
110 };
111 };
111 };
112 PasteDeploy = super.buildPythonPackage {
112 PasteDeploy = super.buildPythonPackage {
113 name = "PasteDeploy-1.5.2";
113 name = "PasteDeploy-1.5.2";
114 buildInputs = with self; [];
114 buildInputs = with self; [];
115 doCheck = false;
115 doCheck = false;
116 propagatedBuildInputs = with self; [];
116 propagatedBuildInputs = with self; [];
117 src = fetchurl {
117 src = fetchurl {
118 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
118 url = "https://pypi.python.org/packages/0f/90/8e20cdae206c543ea10793cbf4136eb9a8b3f417e04e40a29d72d9922cbd/PasteDeploy-1.5.2.tar.gz";
119 md5 = "352b7205c78c8de4987578d19431af3b";
119 md5 = "352b7205c78c8de4987578d19431af3b";
120 };
120 };
121 };
121 };
122 PasteScript = super.buildPythonPackage {
122 PasteScript = super.buildPythonPackage {
123 name = "PasteScript-1.7.5";
123 name = "PasteScript-1.7.5";
124 buildInputs = with self; [];
124 buildInputs = with self; [];
125 doCheck = false;
125 doCheck = false;
126 propagatedBuildInputs = with self; [Paste PasteDeploy];
126 propagatedBuildInputs = with self; [Paste PasteDeploy];
127 src = fetchurl {
127 src = fetchurl {
128 url = "https://pypi.python.org/packages/a5/05/fc60efa7c2f17a1dbaeccb2a903a1e90902d92b9d00eebabe3095829d806/PasteScript-1.7.5.tar.gz";
128 url = "https://pypi.python.org/packages/a5/05/fc60efa7c2f17a1dbaeccb2a903a1e90902d92b9d00eebabe3095829d806/PasteScript-1.7.5.tar.gz";
129 md5 = "4c72d78dcb6bb993f30536842c16af4d";
129 md5 = "4c72d78dcb6bb993f30536842c16af4d";
130 };
130 };
131 };
131 };
132 Pygments = super.buildPythonPackage {
132 Pygments = super.buildPythonPackage {
133 name = "Pygments-2.0.2";
133 name = "Pygments-2.0.2";
134 buildInputs = with self; [];
134 buildInputs = with self; [];
135 doCheck = false;
135 doCheck = false;
136 propagatedBuildInputs = with self; [];
136 propagatedBuildInputs = with self; [];
137 src = fetchurl {
137 src = fetchurl {
138 url = "https://pypi.python.org/packages/f4/c6/bdbc5a8a112256b2b6136af304dbae93d8b1ef8738ff2d12a51018800e46/Pygments-2.0.2.tar.gz";
138 url = "https://pypi.python.org/packages/f4/c6/bdbc5a8a112256b2b6136af304dbae93d8b1ef8738ff2d12a51018800e46/Pygments-2.0.2.tar.gz";
139 md5 = "238587a1370d62405edabd0794b3ec4a";
139 md5 = "238587a1370d62405edabd0794b3ec4a";
140 };
140 };
141 };
141 };
142 Pylons = super.buildPythonPackage {
142 Pylons = super.buildPythonPackage {
143 name = "Pylons-1.0.1";
143 name = "Pylons-1.0.1";
144 buildInputs = with self; [];
144 buildInputs = with self; [];
145 doCheck = false;
145 doCheck = false;
146 propagatedBuildInputs = with self; [Routes WebHelpers Beaker Paste PasteDeploy PasteScript FormEncode simplejson decorator nose Mako WebError WebTest Tempita MarkupSafe WebOb];
146 propagatedBuildInputs = with self; [Routes WebHelpers Beaker Paste PasteDeploy PasteScript FormEncode simplejson decorator nose Mako WebError WebTest Tempita MarkupSafe WebOb];
147 src = fetchurl {
147 src = fetchurl {
148 url = "https://pypi.python.org/packages/a2/69/b835a6bad00acbfeed3f33c6e44fa3f936efc998c795bfb15c61a79ecf62/Pylons-1.0.1.tar.gz";
148 url = "https://pypi.python.org/packages/a2/69/b835a6bad00acbfeed3f33c6e44fa3f936efc998c795bfb15c61a79ecf62/Pylons-1.0.1.tar.gz";
149 md5 = "6cb880d75fa81213192142b07a6e4915";
149 md5 = "6cb880d75fa81213192142b07a6e4915";
150 };
150 };
151 };
151 };
152 Pyro4 = super.buildPythonPackage {
152 Pyro4 = super.buildPythonPackage {
153 name = "Pyro4-4.41";
153 name = "Pyro4-4.41";
154 buildInputs = with self; [];
154 buildInputs = with self; [];
155 doCheck = false;
155 doCheck = false;
156 propagatedBuildInputs = with self; [serpent];
156 propagatedBuildInputs = with self; [serpent];
157 src = fetchurl {
157 src = fetchurl {
158 url = "https://pypi.python.org/packages/56/2b/89b566b4bf3e7f8ba790db2d1223852f8cb454c52cab7693dd41f608ca2a/Pyro4-4.41.tar.gz";
158 url = "https://pypi.python.org/packages/56/2b/89b566b4bf3e7f8ba790db2d1223852f8cb454c52cab7693dd41f608ca2a/Pyro4-4.41.tar.gz";
159 md5 = "ed69e9bfafa9c06c049a87cb0c4c2b6c";
159 md5 = "ed69e9bfafa9c06c049a87cb0c4c2b6c";
160 };
160 };
161 };
161 };
162 Routes = super.buildPythonPackage {
162 Routes = super.buildPythonPackage {
163 name = "Routes-1.13";
163 name = "Routes-1.13";
164 buildInputs = with self; [];
164 buildInputs = with self; [];
165 doCheck = false;
165 doCheck = false;
166 propagatedBuildInputs = with self; [repoze.lru];
166 propagatedBuildInputs = with self; [repoze.lru];
167 src = fetchurl {
167 src = fetchurl {
168 url = "https://pypi.python.org/packages/88/d3/259c3b3cde8837eb9441ab5f574a660e8a4acea8f54a078441d4d2acac1c/Routes-1.13.tar.gz";
168 url = "https://pypi.python.org/packages/88/d3/259c3b3cde8837eb9441ab5f574a660e8a4acea8f54a078441d4d2acac1c/Routes-1.13.tar.gz";
169 md5 = "d527b0ab7dd9172b1275a41f97448783";
169 md5 = "d527b0ab7dd9172b1275a41f97448783";
170 };
170 };
171 };
171 };
172 SQLAlchemy = super.buildPythonPackage {
172 SQLAlchemy = super.buildPythonPackage {
173 name = "SQLAlchemy-0.9.9";
173 name = "SQLAlchemy-0.9.9";
174 buildInputs = with self; [];
174 buildInputs = with self; [];
175 doCheck = false;
175 doCheck = false;
176 propagatedBuildInputs = with self; [];
176 propagatedBuildInputs = with self; [];
177 src = fetchurl {
177 src = fetchurl {
178 url = "https://pypi.python.org/packages/28/f7/1bbfd0d8597e8c358d5e15a166a486ad82fc5579b4e67b6ef7c05b1d182b/SQLAlchemy-0.9.9.tar.gz";
178 url = "https://pypi.python.org/packages/28/f7/1bbfd0d8597e8c358d5e15a166a486ad82fc5579b4e67b6ef7c05b1d182b/SQLAlchemy-0.9.9.tar.gz";
179 md5 = "8a10a9bd13ed3336ef7333ac2cc679ff";
179 md5 = "8a10a9bd13ed3336ef7333ac2cc679ff";
180 };
180 };
181 };
181 };
182 Sphinx = super.buildPythonPackage {
182 Sphinx = super.buildPythonPackage {
183 name = "Sphinx-1.2.2";
183 name = "Sphinx-1.2.2";
184 buildInputs = with self; [];
184 buildInputs = with self; [];
185 doCheck = false;
185 doCheck = false;
186 propagatedBuildInputs = with self; [Pygments docutils Jinja2];
186 propagatedBuildInputs = with self; [Pygments docutils Jinja2];
187 src = fetchurl {
187 src = fetchurl {
188 url = "https://pypi.python.org/packages/0a/50/34017e6efcd372893a416aba14b84a1a149fc7074537b0e9cb6ca7b7abe9/Sphinx-1.2.2.tar.gz";
188 url = "https://pypi.python.org/packages/0a/50/34017e6efcd372893a416aba14b84a1a149fc7074537b0e9cb6ca7b7abe9/Sphinx-1.2.2.tar.gz";
189 md5 = "3dc73ccaa8d0bfb2d62fb671b1f7e8a4";
189 md5 = "3dc73ccaa8d0bfb2d62fb671b1f7e8a4";
190 };
190 };
191 };
191 };
192 Tempita = super.buildPythonPackage {
192 Tempita = super.buildPythonPackage {
193 name = "Tempita-0.5.2";
193 name = "Tempita-0.5.2";
194 buildInputs = with self; [];
194 buildInputs = with self; [];
195 doCheck = false;
195 doCheck = false;
196 propagatedBuildInputs = with self; [];
196 propagatedBuildInputs = with self; [];
197 src = fetchurl {
197 src = fetchurl {
198 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
198 url = "https://pypi.python.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
199 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
199 md5 = "4c2f17bb9d481821c41b6fbee904cea1";
200 };
200 };
201 };
201 };
202 URLObject = super.buildPythonPackage {
202 URLObject = super.buildPythonPackage {
203 name = "URLObject-2.4.0";
203 name = "URLObject-2.4.0";
204 buildInputs = with self; [];
204 buildInputs = with self; [];
205 doCheck = false;
205 doCheck = false;
206 propagatedBuildInputs = with self; [];
206 propagatedBuildInputs = with self; [];
207 src = fetchurl {
207 src = fetchurl {
208 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
208 url = "https://pypi.python.org/packages/cb/b6/e25e58500f9caef85d664bec71ec67c116897bfebf8622c32cb75d1ca199/URLObject-2.4.0.tar.gz";
209 md5 = "2ed819738a9f0a3051f31dc9924e3065";
209 md5 = "2ed819738a9f0a3051f31dc9924e3065";
210 };
210 };
211 };
211 };
212 WebError = super.buildPythonPackage {
212 WebError = super.buildPythonPackage {
213 name = "WebError-0.10.3";
213 name = "WebError-0.10.3";
214 buildInputs = with self; [];
214 buildInputs = with self; [];
215 doCheck = false;
215 doCheck = false;
216 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
216 propagatedBuildInputs = with self; [WebOb Tempita Pygments Paste];
217 src = fetchurl {
217 src = fetchurl {
218 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
218 url = "https://pypi.python.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
219 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
219 md5 = "84b9990b0baae6fd440b1e60cdd06f9a";
220 };
220 };
221 };
221 };
222 WebHelpers = super.buildPythonPackage {
222 WebHelpers = super.buildPythonPackage {
223 name = "WebHelpers-1.3";
223 name = "WebHelpers-1.3";
224 buildInputs = with self; [];
224 buildInputs = with self; [];
225 doCheck = false;
225 doCheck = false;
226 propagatedBuildInputs = with self; [MarkupSafe];
226 propagatedBuildInputs = with self; [MarkupSafe];
227 src = fetchurl {
227 src = fetchurl {
228 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
228 url = "https://pypi.python.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
229 md5 = "32749ffadfc40fea51075a7def32588b";
229 md5 = "32749ffadfc40fea51075a7def32588b";
230 };
230 };
231 };
231 };
232 WebHelpers2 = super.buildPythonPackage {
232 WebHelpers2 = super.buildPythonPackage {
233 name = "WebHelpers2-2.0";
233 name = "WebHelpers2-2.0";
234 buildInputs = with self; [];
234 buildInputs = with self; [];
235 doCheck = false;
235 doCheck = false;
236 propagatedBuildInputs = with self; [MarkupSafe six];
236 propagatedBuildInputs = with self; [MarkupSafe six];
237 src = fetchurl {
237 src = fetchurl {
238 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
238 url = "https://pypi.python.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
239 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
239 md5 = "0f6b68d70c12ee0aed48c00b24da13d3";
240 };
240 };
241 };
241 };
242 WebOb = super.buildPythonPackage {
242 WebOb = super.buildPythonPackage {
243 name = "WebOb-1.3.1";
243 name = "WebOb-1.3.1";
244 buildInputs = with self; [];
244 buildInputs = with self; [];
245 doCheck = false;
245 doCheck = false;
246 propagatedBuildInputs = with self; [];
246 propagatedBuildInputs = with self; [];
247 src = fetchurl {
247 src = fetchurl {
248 url = "https://pypi.python.org/packages/16/78/adfc0380b8a0d75b2d543fa7085ba98a573b1ae486d9def88d172b81b9fa/WebOb-1.3.1.tar.gz";
248 url = "https://pypi.python.org/packages/16/78/adfc0380b8a0d75b2d543fa7085ba98a573b1ae486d9def88d172b81b9fa/WebOb-1.3.1.tar.gz";
249 md5 = "20918251c5726956ba8fef22d1556177";
249 md5 = "20918251c5726956ba8fef22d1556177";
250 };
250 };
251 };
251 };
252 WebTest = super.buildPythonPackage {
252 WebTest = super.buildPythonPackage {
253 name = "WebTest-1.4.3";
253 name = "WebTest-1.4.3";
254 buildInputs = with self; [];
254 buildInputs = with self; [];
255 doCheck = false;
255 doCheck = false;
256 propagatedBuildInputs = with self; [WebOb];
256 propagatedBuildInputs = with self; [WebOb];
257 src = fetchurl {
257 src = fetchurl {
258 url = "https://pypi.python.org/packages/51/3d/84fd0f628df10b30c7db87895f56d0158e5411206b721ca903cb51bfd948/WebTest-1.4.3.zip";
258 url = "https://pypi.python.org/packages/51/3d/84fd0f628df10b30c7db87895f56d0158e5411206b721ca903cb51bfd948/WebTest-1.4.3.zip";
259 md5 = "631ce728bed92c681a4020a36adbc353";
259 md5 = "631ce728bed92c681a4020a36adbc353";
260 };
260 };
261 };
261 };
262 Whoosh = super.buildPythonPackage {
262 Whoosh = super.buildPythonPackage {
263 name = "Whoosh-2.7.0";
263 name = "Whoosh-2.7.0";
264 buildInputs = with self; [];
264 buildInputs = with self; [];
265 doCheck = false;
265 doCheck = false;
266 propagatedBuildInputs = with self; [];
266 propagatedBuildInputs = with self; [];
267 src = fetchurl {
267 src = fetchurl {
268 url = "https://pypi.python.org/packages/1c/dc/2f0231ff3875ded36df8c1ab851451e51a237dc0e5a86d3d96036158da94/Whoosh-2.7.0.zip";
268 url = "https://pypi.python.org/packages/1c/dc/2f0231ff3875ded36df8c1ab851451e51a237dc0e5a86d3d96036158da94/Whoosh-2.7.0.zip";
269 md5 = "7abfd970f16fadc7311960f3fa0bc7a9";
269 md5 = "7abfd970f16fadc7311960f3fa0bc7a9";
270 };
270 };
271 };
271 };
272 alembic = super.buildPythonPackage {
272 alembic = super.buildPythonPackage {
273 name = "alembic-0.8.4";
273 name = "alembic-0.8.4";
274 buildInputs = with self; [];
274 buildInputs = with self; [];
275 doCheck = false;
275 doCheck = false;
276 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor];
276 propagatedBuildInputs = with self; [SQLAlchemy Mako python-editor];
277 src = fetchurl {
277 src = fetchurl {
278 url = "https://pypi.python.org/packages/ca/7e/299b4499b5c75e5a38c5845145ad24755bebfb8eec07a2e1c366b7181eeb/alembic-0.8.4.tar.gz";
278 url = "https://pypi.python.org/packages/ca/7e/299b4499b5c75e5a38c5845145ad24755bebfb8eec07a2e1c366b7181eeb/alembic-0.8.4.tar.gz";
279 md5 = "5f95d8ee62b443f9b37eb5bee76c582d";
279 md5 = "5f95d8ee62b443f9b37eb5bee76c582d";
280 };
280 };
281 };
281 };
282 amqplib = super.buildPythonPackage {
282 amqplib = super.buildPythonPackage {
283 name = "amqplib-1.0.2";
283 name = "amqplib-1.0.2";
284 buildInputs = with self; [];
284 buildInputs = with self; [];
285 doCheck = false;
285 doCheck = false;
286 propagatedBuildInputs = with self; [];
286 propagatedBuildInputs = with self; [];
287 src = fetchurl {
287 src = fetchurl {
288 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
288 url = "https://pypi.python.org/packages/75/b7/8c2429bf8d92354a0118614f9a4d15e53bc69ebedce534284111de5a0102/amqplib-1.0.2.tgz";
289 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
289 md5 = "5c92f17fbedd99b2b4a836d4352d1e2f";
290 };
290 };
291 };
291 };
292 anyjson = super.buildPythonPackage {
292 anyjson = super.buildPythonPackage {
293 name = "anyjson-0.3.3";
293 name = "anyjson-0.3.3";
294 buildInputs = with self; [];
294 buildInputs = with self; [];
295 doCheck = false;
295 doCheck = false;
296 propagatedBuildInputs = with self; [];
296 propagatedBuildInputs = with self; [];
297 src = fetchurl {
297 src = fetchurl {
298 url = "https://pypi.python.org/packages/c3/4d/d4089e1a3dd25b46bebdb55a992b0797cff657b4477bc32ce28038fdecbc/anyjson-0.3.3.tar.gz";
298 url = "https://pypi.python.org/packages/c3/4d/d4089e1a3dd25b46bebdb55a992b0797cff657b4477bc32ce28038fdecbc/anyjson-0.3.3.tar.gz";
299 md5 = "2ea28d6ec311aeeebaf993cb3008b27c";
299 md5 = "2ea28d6ec311aeeebaf993cb3008b27c";
300 };
300 };
301 };
301 };
302 appenlight-client = super.buildPythonPackage {
302 appenlight-client = super.buildPythonPackage {
303 name = "appenlight-client-0.6.14";
303 name = "appenlight-client-0.6.14";
304 buildInputs = with self; [];
304 buildInputs = with self; [];
305 doCheck = false;
305 doCheck = false;
306 propagatedBuildInputs = with self; [WebOb requests];
306 propagatedBuildInputs = with self; [WebOb requests];
307 src = fetchurl {
307 src = fetchurl {
308 url = "https://pypi.python.org/packages/4d/e0/23fee3ebada8143f707e65c06bcb82992040ee64ea8355e044ed55ebf0c1/appenlight_client-0.6.14.tar.gz";
308 url = "https://pypi.python.org/packages/4d/e0/23fee3ebada8143f707e65c06bcb82992040ee64ea8355e044ed55ebf0c1/appenlight_client-0.6.14.tar.gz";
309 md5 = "578c69b09f4356d898fff1199b98a95c";
309 md5 = "578c69b09f4356d898fff1199b98a95c";
310 };
310 };
311 };
311 };
312 authomatic = super.buildPythonPackage {
312 authomatic = super.buildPythonPackage {
313 name = "authomatic-0.1.0.post1";
313 name = "authomatic-0.1.0.post1";
314 buildInputs = with self; [];
314 buildInputs = with self; [];
315 doCheck = false;
315 doCheck = false;
316 propagatedBuildInputs = with self; [];
316 propagatedBuildInputs = with self; [];
317 src = fetchurl {
317 src = fetchurl {
318 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
318 url = "https://pypi.python.org/packages/08/1a/8a930461e604c2d5a7a871e1ac59fa82ccf994c32e807230c8d2fb07815a/Authomatic-0.1.0.post1.tar.gz";
319 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
319 md5 = "be3f3ce08747d776aae6d6cc8dcb49a9";
320 };
320 };
321 };
321 };
322 backport-ipaddress = super.buildPythonPackage {
322 backport-ipaddress = super.buildPythonPackage {
323 name = "backport-ipaddress-0.1";
323 name = "backport-ipaddress-0.1";
324 buildInputs = with self; [];
324 buildInputs = with self; [];
325 doCheck = false;
325 doCheck = false;
326 propagatedBuildInputs = with self; [];
326 propagatedBuildInputs = with self; [];
327 src = fetchurl {
327 src = fetchurl {
328 url = "https://pypi.python.org/packages/d3/30/54c6dab05a4dec44db25ff309f1fbb6b7a8bde3f2bade38bb9da67bbab8f/backport_ipaddress-0.1.tar.gz";
328 url = "https://pypi.python.org/packages/d3/30/54c6dab05a4dec44db25ff309f1fbb6b7a8bde3f2bade38bb9da67bbab8f/backport_ipaddress-0.1.tar.gz";
329 md5 = "9c1f45f4361f71b124d7293a60006c05";
329 md5 = "9c1f45f4361f71b124d7293a60006c05";
330 };
330 };
331 };
331 };
332 bottle = super.buildPythonPackage {
332 bottle = super.buildPythonPackage {
333 name = "bottle-0.12.8";
333 name = "bottle-0.12.8";
334 buildInputs = with self; [];
334 buildInputs = with self; [];
335 doCheck = false;
335 doCheck = false;
336 propagatedBuildInputs = with self; [];
336 propagatedBuildInputs = with self; [];
337 src = fetchurl {
337 src = fetchurl {
338 url = "https://pypi.python.org/packages/52/df/e4a408f3a7af396d186d4ecd3b389dd764f0f943b4fa8d257bfe7b49d343/bottle-0.12.8.tar.gz";
338 url = "https://pypi.python.org/packages/52/df/e4a408f3a7af396d186d4ecd3b389dd764f0f943b4fa8d257bfe7b49d343/bottle-0.12.8.tar.gz";
339 md5 = "13132c0a8f607bf860810a6ee9064c5b";
339 md5 = "13132c0a8f607bf860810a6ee9064c5b";
340 };
340 };
341 };
341 };
342 bumpversion = super.buildPythonPackage {
342 bumpversion = super.buildPythonPackage {
343 name = "bumpversion-0.5.3";
343 name = "bumpversion-0.5.3";
344 buildInputs = with self; [];
344 buildInputs = with self; [];
345 doCheck = false;
345 doCheck = false;
346 propagatedBuildInputs = with self; [];
346 propagatedBuildInputs = with self; [];
347 src = fetchurl {
347 src = fetchurl {
348 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
348 url = "https://pypi.python.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
349 md5 = "c66a3492eafcf5ad4b024be9fca29820";
349 md5 = "c66a3492eafcf5ad4b024be9fca29820";
350 };
350 };
351 };
351 };
352 celery = super.buildPythonPackage {
352 celery = super.buildPythonPackage {
353 name = "celery-2.2.10";
353 name = "celery-2.2.10";
354 buildInputs = with self; [];
354 buildInputs = with self; [];
355 doCheck = false;
355 doCheck = false;
356 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
356 propagatedBuildInputs = with self; [python-dateutil anyjson kombu pyparsing];
357 src = fetchurl {
357 src = fetchurl {
358 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
358 url = "https://pypi.python.org/packages/b1/64/860fd50e45844c83442e7953effcddeff66b2851d90b2d784f7201c111b8/celery-2.2.10.tar.gz";
359 md5 = "898bc87e54f278055b561316ba73e222";
359 md5 = "898bc87e54f278055b561316ba73e222";
360 };
360 };
361 };
361 };
362 certifi = super.buildPythonPackage {
363 name = "certifi-2016.2.28";
364 buildInputs = with self; [];
365 doCheck = false;
366 propagatedBuildInputs = with self; [];
367 src = fetchurl {
368 url = "https://pypi.python.org/packages/5c/f8/f6c54727c74579c6bbe5926f5deb9677c5810a33e11da58d1a4e2d09d041/certifi-2016.2.28.tar.gz";
369 md5 = "5d672aa766e1f773c75cfeccd02d3650";
370 };
371 };
372 click = super.buildPythonPackage {
362 click = super.buildPythonPackage {
373 name = "click-5.1";
363 name = "click-5.1";
374 buildInputs = with self; [];
364 buildInputs = with self; [];
375 doCheck = false;
365 doCheck = false;
376 propagatedBuildInputs = with self; [];
366 propagatedBuildInputs = with self; [];
377 src = fetchurl {
367 src = fetchurl {
378 url = "https://pypi.python.org/packages/b7/34/a496632c4fb6c1ee76efedf77bb8d28b29363d839953d95095b12defe791/click-5.1.tar.gz";
368 url = "https://pypi.python.org/packages/b7/34/a496632c4fb6c1ee76efedf77bb8d28b29363d839953d95095b12defe791/click-5.1.tar.gz";
379 md5 = "9c5323008cccfe232a8b161fc8196d41";
369 md5 = "9c5323008cccfe232a8b161fc8196d41";
380 };
370 };
381 };
371 };
382 colander = super.buildPythonPackage {
372 colander = super.buildPythonPackage {
383 name = "colander-1.2";
373 name = "colander-1.2";
384 buildInputs = with self; [];
374 buildInputs = with self; [];
385 doCheck = false;
375 doCheck = false;
386 propagatedBuildInputs = with self; [translationstring iso8601];
376 propagatedBuildInputs = with self; [translationstring iso8601];
387 src = fetchurl {
377 src = fetchurl {
388 url = "https://pypi.python.org/packages/14/23/c9ceba07a6a1dc0eefbb215fc0dc64aabc2b22ee756bc0f0c13278fa0887/colander-1.2.tar.gz";
378 url = "https://pypi.python.org/packages/14/23/c9ceba07a6a1dc0eefbb215fc0dc64aabc2b22ee756bc0f0c13278fa0887/colander-1.2.tar.gz";
389 md5 = "83db21b07936a0726e588dae1914b9ed";
379 md5 = "83db21b07936a0726e588dae1914b9ed";
390 };
380 };
391 };
381 };
392 configobj = super.buildPythonPackage {
382 configobj = super.buildPythonPackage {
393 name = "configobj-5.0.6";
383 name = "configobj-5.0.6";
394 buildInputs = with self; [];
384 buildInputs = with self; [];
395 doCheck = false;
385 doCheck = false;
396 propagatedBuildInputs = with self; [six];
386 propagatedBuildInputs = with self; [six];
397 src = fetchurl {
387 src = fetchurl {
398 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
388 url = "https://pypi.python.org/packages/64/61/079eb60459c44929e684fa7d9e2fdca403f67d64dd9dbac27296be2e0fab/configobj-5.0.6.tar.gz";
399 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
389 md5 = "e472a3a1c2a67bb0ec9b5d54c13a47d6";
400 };
390 };
401 };
391 };
402 cov-core = super.buildPythonPackage {
392 cov-core = super.buildPythonPackage {
403 name = "cov-core-1.15.0";
393 name = "cov-core-1.15.0";
404 buildInputs = with self; [];
394 buildInputs = with self; [];
405 doCheck = false;
395 doCheck = false;
406 propagatedBuildInputs = with self; [coverage];
396 propagatedBuildInputs = with self; [coverage];
407 src = fetchurl {
397 src = fetchurl {
408 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
398 url = "https://pypi.python.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
409 md5 = "f519d4cb4c4e52856afb14af52919fe6";
399 md5 = "f519d4cb4c4e52856afb14af52919fe6";
410 };
400 };
411 };
401 };
412 coverage = super.buildPythonPackage {
402 coverage = super.buildPythonPackage {
413 name = "coverage-3.7.1";
403 name = "coverage-3.7.1";
414 buildInputs = with self; [];
404 buildInputs = with self; [];
415 doCheck = false;
405 doCheck = false;
416 propagatedBuildInputs = with self; [];
406 propagatedBuildInputs = with self; [];
417 src = fetchurl {
407 src = fetchurl {
418 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
408 url = "https://pypi.python.org/packages/09/4f/89b06c7fdc09687bca507dc411c342556ef9c5a3b26756137a4878ff19bf/coverage-3.7.1.tar.gz";
419 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
409 md5 = "c47b36ceb17eaff3ecfab3bcd347d0df";
420 };
410 };
421 };
411 };
422 cssselect = super.buildPythonPackage {
412 cssselect = super.buildPythonPackage {
423 name = "cssselect-0.9.1";
413 name = "cssselect-0.9.1";
424 buildInputs = with self; [];
414 buildInputs = with self; [];
425 doCheck = false;
415 doCheck = false;
426 propagatedBuildInputs = with self; [];
416 propagatedBuildInputs = with self; [];
427 src = fetchurl {
417 src = fetchurl {
428 url = "https://pypi.python.org/packages/aa/e5/9ee1460d485b94a6d55732eb7ad5b6c084caf73dd6f9cb0bb7d2a78fafe8/cssselect-0.9.1.tar.gz";
418 url = "https://pypi.python.org/packages/aa/e5/9ee1460d485b94a6d55732eb7ad5b6c084caf73dd6f9cb0bb7d2a78fafe8/cssselect-0.9.1.tar.gz";
429 md5 = "c74f45966277dc7a0f768b9b0f3522ac";
419 md5 = "c74f45966277dc7a0f768b9b0f3522ac";
430 };
420 };
431 };
421 };
432 decorator = super.buildPythonPackage {
422 decorator = super.buildPythonPackage {
433 name = "decorator-3.4.2";
423 name = "decorator-3.4.2";
434 buildInputs = with self; [];
424 buildInputs = with self; [];
435 doCheck = false;
425 doCheck = false;
436 propagatedBuildInputs = with self; [];
426 propagatedBuildInputs = with self; [];
437 src = fetchurl {
427 src = fetchurl {
438 url = "https://pypi.python.org/packages/35/3a/42566eb7a2cbac774399871af04e11d7ae3fc2579e7dae85213b8d1d1c57/decorator-3.4.2.tar.gz";
428 url = "https://pypi.python.org/packages/35/3a/42566eb7a2cbac774399871af04e11d7ae3fc2579e7dae85213b8d1d1c57/decorator-3.4.2.tar.gz";
439 md5 = "9e0536870d2b83ae27d58dbf22582f4d";
429 md5 = "9e0536870d2b83ae27d58dbf22582f4d";
440 };
430 };
441 };
431 };
442 docutils = super.buildPythonPackage {
432 docutils = super.buildPythonPackage {
443 name = "docutils-0.12";
433 name = "docutils-0.12";
444 buildInputs = with self; [];
434 buildInputs = with self; [];
445 doCheck = false;
435 doCheck = false;
446 propagatedBuildInputs = with self; [];
436 propagatedBuildInputs = with self; [];
447 src = fetchurl {
437 src = fetchurl {
448 url = "https://pypi.python.org/packages/37/38/ceda70135b9144d84884ae2fc5886c6baac4edea39550f28bcd144c1234d/docutils-0.12.tar.gz";
438 url = "https://pypi.python.org/packages/37/38/ceda70135b9144d84884ae2fc5886c6baac4edea39550f28bcd144c1234d/docutils-0.12.tar.gz";
449 md5 = "4622263b62c5c771c03502afa3157768";
439 md5 = "4622263b62c5c771c03502afa3157768";
450 };
440 };
451 };
441 };
452 dogpile.cache = super.buildPythonPackage {
442 dogpile.cache = super.buildPythonPackage {
453 name = "dogpile.cache-0.5.7";
443 name = "dogpile.cache-0.5.7";
454 buildInputs = with self; [];
444 buildInputs = with self; [];
455 doCheck = false;
445 doCheck = false;
456 propagatedBuildInputs = with self; [dogpile.core];
446 propagatedBuildInputs = with self; [dogpile.core];
457 src = fetchurl {
447 src = fetchurl {
458 url = "https://pypi.python.org/packages/07/74/2a83bedf758156d9c95d112691bbad870d3b77ccbcfb781b4ef836ea7d96/dogpile.cache-0.5.7.tar.gz";
448 url = "https://pypi.python.org/packages/07/74/2a83bedf758156d9c95d112691bbad870d3b77ccbcfb781b4ef836ea7d96/dogpile.cache-0.5.7.tar.gz";
459 md5 = "3e58ce41af574aab41d78e9c4190f194";
449 md5 = "3e58ce41af574aab41d78e9c4190f194";
460 };
450 };
461 };
451 };
462 dogpile.core = super.buildPythonPackage {
452 dogpile.core = super.buildPythonPackage {
463 name = "dogpile.core-0.4.1";
453 name = "dogpile.core-0.4.1";
464 buildInputs = with self; [];
454 buildInputs = with self; [];
465 doCheck = false;
455 doCheck = false;
466 propagatedBuildInputs = with self; [];
456 propagatedBuildInputs = with self; [];
467 src = fetchurl {
457 src = fetchurl {
468 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
458 url = "https://pypi.python.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
469 md5 = "01cb19f52bba3e95c9b560f39341f045";
459 md5 = "01cb19f52bba3e95c9b560f39341f045";
470 };
460 };
471 };
461 };
472 dulwich = super.buildPythonPackage {
462 dulwich = super.buildPythonPackage {
473 name = "dulwich-0.12.0";
463 name = "dulwich-0.12.0";
474 buildInputs = with self; [];
464 buildInputs = with self; [];
475 doCheck = false;
465 doCheck = false;
476 propagatedBuildInputs = with self; [];
466 propagatedBuildInputs = with self; [];
477 src = fetchurl {
467 src = fetchurl {
478 url = "https://pypi.python.org/packages/6f/04/fbe561b6d45c0ec758330d5b7f5ba4b6cb4f1ca1ab49859d2fc16320da75/dulwich-0.12.0.tar.gz";
468 url = "https://pypi.python.org/packages/6f/04/fbe561b6d45c0ec758330d5b7f5ba4b6cb4f1ca1ab49859d2fc16320da75/dulwich-0.12.0.tar.gz";
479 md5 = "f3a8a12bd9f9dd8c233e18f3d49436fa";
469 md5 = "f3a8a12bd9f9dd8c233e18f3d49436fa";
480 };
470 };
481 };
471 };
482 ecdsa = super.buildPythonPackage {
472 ecdsa = super.buildPythonPackage {
483 name = "ecdsa-0.11";
473 name = "ecdsa-0.11";
484 buildInputs = with self; [];
474 buildInputs = with self; [];
485 doCheck = false;
475 doCheck = false;
486 propagatedBuildInputs = with self; [];
476 propagatedBuildInputs = with self; [];
487 src = fetchurl {
477 src = fetchurl {
488 url = "https://pypi.python.org/packages/6c/3f/92fe5dcdcaa7bd117be21e5520c9a54375112b66ec000d209e9e9519fad1/ecdsa-0.11.tar.gz";
478 url = "https://pypi.python.org/packages/6c/3f/92fe5dcdcaa7bd117be21e5520c9a54375112b66ec000d209e9e9519fad1/ecdsa-0.11.tar.gz";
489 md5 = "8ef586fe4dbb156697d756900cb41d7c";
479 md5 = "8ef586fe4dbb156697d756900cb41d7c";
490 };
480 };
491 };
481 };
492 elasticsearch = super.buildPythonPackage {
482 elasticsearch = super.buildPythonPackage {
493 name = "elasticsearch-1.9.0";
483 name = "elasticsearch-2.3.0";
494 buildInputs = with self; [];
484 buildInputs = with self; [];
495 doCheck = false;
485 doCheck = false;
496 propagatedBuildInputs = with self; [urllib3];
486 propagatedBuildInputs = with self; [urllib3];
497 src = fetchurl {
487 src = fetchurl {
498 url = "https://pypi.python.org/packages/13/9b/540e311b31a10c2a904acfb08030c656047e5c7ba479d35df2799e5dccfe/elasticsearch-1.9.0.tar.gz";
488 url = "https://pypi.python.org/packages/10/35/5fd52c5f0b0ee405ed4b5195e8bce44c5e041787680dc7b94b8071cac600/elasticsearch-2.3.0.tar.gz";
499 md5 = "3550390baea1639479f79758d66ab032";
489 md5 = "2550f3b51629cf1ef9636608af92c340";
490 };
491 };
492 elasticsearch-dsl = super.buildPythonPackage {
493 name = "elasticsearch-dsl-2.0.0";
494 buildInputs = with self; [];
495 doCheck = false;
496 propagatedBuildInputs = with self; [six python-dateutil elasticsearch];
497 src = fetchurl {
498 url = "https://pypi.python.org/packages/4e/5d/e788ae8dbe2ff4d13426db0a027533386a5c276c77a2654dc0e2007ce04a/elasticsearch-dsl-2.0.0.tar.gz";
499 md5 = "4cdfec81bb35383dd3b7d02d7dc5ee68";
500 };
500 };
501 };
501 };
502 flake8 = super.buildPythonPackage {
502 flake8 = super.buildPythonPackage {
503 name = "flake8-2.4.1";
503 name = "flake8-2.4.1";
504 buildInputs = with self; [];
504 buildInputs = with self; [];
505 doCheck = false;
505 doCheck = false;
506 propagatedBuildInputs = with self; [pyflakes pep8 mccabe];
506 propagatedBuildInputs = with self; [pyflakes pep8 mccabe];
507 src = fetchurl {
507 src = fetchurl {
508 url = "https://pypi.python.org/packages/8f/b5/9a73c66c7dba273bac8758398f060c008a25f3e84531063b42503b5d0a95/flake8-2.4.1.tar.gz";
508 url = "https://pypi.python.org/packages/8f/b5/9a73c66c7dba273bac8758398f060c008a25f3e84531063b42503b5d0a95/flake8-2.4.1.tar.gz";
509 md5 = "ed45d3db81a3b7c88bd63c6e37ca1d65";
509 md5 = "ed45d3db81a3b7c88bd63c6e37ca1d65";
510 };
510 };
511 };
511 };
512 future = super.buildPythonPackage {
512 future = super.buildPythonPackage {
513 name = "future-0.14.3";
513 name = "future-0.14.3";
514 buildInputs = with self; [];
514 buildInputs = with self; [];
515 doCheck = false;
515 doCheck = false;
516 propagatedBuildInputs = with self; [];
516 propagatedBuildInputs = with self; [];
517 src = fetchurl {
517 src = fetchurl {
518 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
518 url = "https://pypi.python.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
519 md5 = "e94079b0bd1fc054929e8769fc0f6083";
519 md5 = "e94079b0bd1fc054929e8769fc0f6083";
520 };
520 };
521 };
521 };
522 futures = super.buildPythonPackage {
522 futures = super.buildPythonPackage {
523 name = "futures-3.0.2";
523 name = "futures-3.0.2";
524 buildInputs = with self; [];
524 buildInputs = with self; [];
525 doCheck = false;
525 doCheck = false;
526 propagatedBuildInputs = with self; [];
526 propagatedBuildInputs = with self; [];
527 src = fetchurl {
527 src = fetchurl {
528 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
528 url = "https://pypi.python.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
529 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
529 md5 = "42aaf1e4de48d6e871d77dc1f9d96d5a";
530 };
530 };
531 };
531 };
532 gnureadline = super.buildPythonPackage {
532 gnureadline = super.buildPythonPackage {
533 name = "gnureadline-6.3.3";
533 name = "gnureadline-6.3.3";
534 buildInputs = with self; [];
534 buildInputs = with self; [];
535 doCheck = false;
535 doCheck = false;
536 propagatedBuildInputs = with self; [];
536 propagatedBuildInputs = with self; [];
537 src = fetchurl {
537 src = fetchurl {
538 url = "https://pypi.python.org/packages/3a/ee/2c3f568b0a74974791ac590ec742ef6133e2fbd287a074ba72a53fa5e97c/gnureadline-6.3.3.tar.gz";
538 url = "https://pypi.python.org/packages/3a/ee/2c3f568b0a74974791ac590ec742ef6133e2fbd287a074ba72a53fa5e97c/gnureadline-6.3.3.tar.gz";
539 md5 = "c4af83c9a3fbeac8f2da9b5a7c60e51c";
539 md5 = "c4af83c9a3fbeac8f2da9b5a7c60e51c";
540 };
540 };
541 };
541 };
542 gprof2dot = super.buildPythonPackage {
542 gprof2dot = super.buildPythonPackage {
543 name = "gprof2dot-2015.12.1";
543 name = "gprof2dot-2015.12.01";
544 buildInputs = with self; [];
544 buildInputs = with self; [];
545 doCheck = false;
545 doCheck = false;
546 propagatedBuildInputs = with self; [];
546 propagatedBuildInputs = with self; [];
547 src = fetchurl {
547 src = fetchurl {
548 url = "https://pypi.python.org/packages/b9/34/7bf93c1952d40fa5c95ad963f4d8344b61ef58558632402eca18e6c14127/gprof2dot-2015.12.1.tar.gz";
548 url = "https://pypi.python.org/packages/b9/34/7bf93c1952d40fa5c95ad963f4d8344b61ef58558632402eca18e6c14127/gprof2dot-2015.12.1.tar.gz";
549 md5 = "e23bf4e2f94db032750c193384b4165b";
549 md5 = "e23bf4e2f94db032750c193384b4165b";
550 };
550 };
551 };
551 };
552 greenlet = super.buildPythonPackage {
552 greenlet = super.buildPythonPackage {
553 name = "greenlet-0.4.7";
553 name = "greenlet-0.4.9";
554 buildInputs = with self; [];
554 buildInputs = with self; [];
555 doCheck = false;
555 doCheck = false;
556 propagatedBuildInputs = with self; [];
556 propagatedBuildInputs = with self; [];
557 src = fetchurl {
557 src = fetchurl {
558 url = "https://pypi.python.org/packages/7a/9f/a1a0d9bdf3203ae1502c5a8434fe89d323599d78a106985bc327351a69d4/greenlet-0.4.7.zip";
558 url = "https://pypi.python.org/packages/4e/3d/9d421539b74e33608b245092870156b2e171fb49f2b51390aa4641eecb4a/greenlet-0.4.9.zip";
559 md5 = "c2333a8ff30fa75c5d5ec0e67b461086";
559 md5 = "c6659cdb2a5e591723e629d2eef22e82";
560 };
560 };
561 };
561 };
562 gunicorn = super.buildPythonPackage {
562 gunicorn = super.buildPythonPackage {
563 name = "gunicorn-19.6.0";
563 name = "gunicorn-19.6.0";
564 buildInputs = with self; [];
564 buildInputs = with self; [];
565 doCheck = false;
565 doCheck = false;
566 propagatedBuildInputs = with self; [];
566 propagatedBuildInputs = with self; [];
567 src = fetchurl {
567 src = fetchurl {
568 url = "https://pypi.python.org/packages/84/ce/7ea5396efad1cef682bbc4068e72a0276341d9d9d0f501da609fab9fcb80/gunicorn-19.6.0.tar.gz";
568 url = "https://pypi.python.org/packages/84/ce/7ea5396efad1cef682bbc4068e72a0276341d9d9d0f501da609fab9fcb80/gunicorn-19.6.0.tar.gz";
569 md5 = "338e5e8a83ea0f0625f768dba4597530";
569 md5 = "338e5e8a83ea0f0625f768dba4597530";
570 };
570 };
571 };
571 };
572 infrae.cache = super.buildPythonPackage {
572 infrae.cache = super.buildPythonPackage {
573 name = "infrae.cache-1.0.1";
573 name = "infrae.cache-1.0.1";
574 buildInputs = with self; [];
574 buildInputs = with self; [];
575 doCheck = false;
575 doCheck = false;
576 propagatedBuildInputs = with self; [Beaker repoze.lru];
576 propagatedBuildInputs = with self; [Beaker repoze.lru];
577 src = fetchurl {
577 src = fetchurl {
578 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
578 url = "https://pypi.python.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
579 md5 = "b09076a766747e6ed2a755cc62088e32";
579 md5 = "b09076a766747e6ed2a755cc62088e32";
580 };
580 };
581 };
581 };
582 invoke = super.buildPythonPackage {
582 invoke = super.buildPythonPackage {
583 name = "invoke-0.11.1";
583 name = "invoke-0.11.1";
584 buildInputs = with self; [];
584 buildInputs = with self; [];
585 doCheck = false;
585 doCheck = false;
586 propagatedBuildInputs = with self; [];
586 propagatedBuildInputs = with self; [];
587 src = fetchurl {
587 src = fetchurl {
588 url = "https://pypi.python.org/packages/d3/bb/36a5558ea19882073def7b0edeef4a0e6282056fed96506dd10b1d532bd4/invoke-0.11.1.tar.gz";
588 url = "https://pypi.python.org/packages/d3/bb/36a5558ea19882073def7b0edeef4a0e6282056fed96506dd10b1d532bd4/invoke-0.11.1.tar.gz";
589 md5 = "3d4ecbe26779ceef1046ecf702c9c4a8";
589 md5 = "3d4ecbe26779ceef1046ecf702c9c4a8";
590 };
590 };
591 };
591 };
592 ipdb = super.buildPythonPackage {
592 ipdb = super.buildPythonPackage {
593 name = "ipdb-0.8";
593 name = "ipdb-0.8";
594 buildInputs = with self; [];
594 buildInputs = with self; [];
595 doCheck = false;
595 doCheck = false;
596 propagatedBuildInputs = with self; [ipython];
596 propagatedBuildInputs = with self; [ipython];
597 src = fetchurl {
597 src = fetchurl {
598 url = "https://pypi.python.org/packages/f0/25/d7dd430ced6cd8dc242a933c8682b5dbf32eb4011d82f87e34209e5ec845/ipdb-0.8.zip";
598 url = "https://pypi.python.org/packages/f0/25/d7dd430ced6cd8dc242a933c8682b5dbf32eb4011d82f87e34209e5ec845/ipdb-0.8.zip";
599 md5 = "96dca0712efa01aa5eaf6b22071dd3ed";
599 md5 = "96dca0712efa01aa5eaf6b22071dd3ed";
600 };
600 };
601 };
601 };
602 ipython = super.buildPythonPackage {
602 ipython = super.buildPythonPackage {
603 name = "ipython-3.1.0";
603 name = "ipython-3.1.0";
604 buildInputs = with self; [];
604 buildInputs = with self; [];
605 doCheck = false;
605 doCheck = false;
606 propagatedBuildInputs = with self; [gnureadline];
606 propagatedBuildInputs = with self; [];
607 src = fetchurl {
607 src = fetchurl {
608 url = "https://pypi.python.org/packages/06/91/120c0835254c120af89f066afaabf81289bc2726c1fc3ca0555df6882f58/ipython-3.1.0.tar.gz";
608 url = "https://pypi.python.org/packages/06/91/120c0835254c120af89f066afaabf81289bc2726c1fc3ca0555df6882f58/ipython-3.1.0.tar.gz";
609 md5 = "a749d90c16068687b0ec45a27e72ef8f";
609 md5 = "a749d90c16068687b0ec45a27e72ef8f";
610 };
610 };
611 };
611 };
612 iso8601 = super.buildPythonPackage {
612 iso8601 = super.buildPythonPackage {
613 name = "iso8601-0.1.11";
613 name = "iso8601-0.1.11";
614 buildInputs = with self; [];
614 buildInputs = with self; [];
615 doCheck = false;
615 doCheck = false;
616 propagatedBuildInputs = with self; [];
616 propagatedBuildInputs = with self; [];
617 src = fetchurl {
617 src = fetchurl {
618 url = "https://pypi.python.org/packages/c0/75/c9209ee4d1b5975eb8c2cba4428bde6b61bd55664a98290dd015cdb18e98/iso8601-0.1.11.tar.gz";
618 url = "https://pypi.python.org/packages/c0/75/c9209ee4d1b5975eb8c2cba4428bde6b61bd55664a98290dd015cdb18e98/iso8601-0.1.11.tar.gz";
619 md5 = "b06d11cd14a64096f907086044f0fe38";
619 md5 = "b06d11cd14a64096f907086044f0fe38";
620 };
620 };
621 };
621 };
622 itsdangerous = super.buildPythonPackage {
622 itsdangerous = super.buildPythonPackage {
623 name = "itsdangerous-0.24";
623 name = "itsdangerous-0.24";
624 buildInputs = with self; [];
624 buildInputs = with self; [];
625 doCheck = false;
625 doCheck = false;
626 propagatedBuildInputs = with self; [];
626 propagatedBuildInputs = with self; [];
627 src = fetchurl {
627 src = fetchurl {
628 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
628 url = "https://pypi.python.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
629 md5 = "a3d55aa79369aef5345c036a8a26307f";
629 md5 = "a3d55aa79369aef5345c036a8a26307f";
630 };
630 };
631 };
631 };
632 kombu = super.buildPythonPackage {
632 kombu = super.buildPythonPackage {
633 name = "kombu-1.5.1";
633 name = "kombu-1.5.1";
634 buildInputs = with self; [];
634 buildInputs = with self; [];
635 doCheck = false;
635 doCheck = false;
636 propagatedBuildInputs = with self; [anyjson amqplib];
636 propagatedBuildInputs = with self; [anyjson amqplib];
637 src = fetchurl {
637 src = fetchurl {
638 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
638 url = "https://pypi.python.org/packages/19/53/74bf2a624644b45f0850a638752514fc10a8e1cbd738f10804951a6df3f5/kombu-1.5.1.tar.gz";
639 md5 = "50662f3c7e9395b3d0721fb75d100b63";
639 md5 = "50662f3c7e9395b3d0721fb75d100b63";
640 };
640 };
641 };
641 };
642 lxml = super.buildPythonPackage {
642 lxml = super.buildPythonPackage {
643 name = "lxml-3.4.4";
643 name = "lxml-3.4.4";
644 buildInputs = with self; [];
644 buildInputs = with self; [];
645 doCheck = false;
645 doCheck = false;
646 propagatedBuildInputs = with self; [];
646 propagatedBuildInputs = with self; [];
647 src = fetchurl {
647 src = fetchurl {
648 url = "https://pypi.python.org/packages/63/c7/4f2a2a4ad6c6fa99b14be6b3c1cece9142e2d915aa7c43c908677afc8fa4/lxml-3.4.4.tar.gz";
648 url = "https://pypi.python.org/packages/63/c7/4f2a2a4ad6c6fa99b14be6b3c1cece9142e2d915aa7c43c908677afc8fa4/lxml-3.4.4.tar.gz";
649 md5 = "a9a65972afc173ec7a39c585f4eea69c";
649 md5 = "a9a65972afc173ec7a39c585f4eea69c";
650 };
650 };
651 };
651 };
652 mccabe = super.buildPythonPackage {
652 mccabe = super.buildPythonPackage {
653 name = "mccabe-0.3";
653 name = "mccabe-0.3";
654 buildInputs = with self; [];
654 buildInputs = with self; [];
655 doCheck = false;
655 doCheck = false;
656 propagatedBuildInputs = with self; [];
656 propagatedBuildInputs = with self; [];
657 src = fetchurl {
657 src = fetchurl {
658 url = "https://pypi.python.org/packages/c9/2e/75231479e11a906b64ac43bad9d0bb534d00080b18bdca8db9da46e1faf7/mccabe-0.3.tar.gz";
658 url = "https://pypi.python.org/packages/c9/2e/75231479e11a906b64ac43bad9d0bb534d00080b18bdca8db9da46e1faf7/mccabe-0.3.tar.gz";
659 md5 = "81640948ff226f8c12b3277059489157";
659 md5 = "81640948ff226f8c12b3277059489157";
660 };
660 };
661 };
661 };
662 meld3 = super.buildPythonPackage {
662 meld3 = super.buildPythonPackage {
663 name = "meld3-1.0.2";
663 name = "meld3-1.0.2";
664 buildInputs = with self; [];
664 buildInputs = with self; [];
665 doCheck = false;
665 doCheck = false;
666 propagatedBuildInputs = with self; [];
666 propagatedBuildInputs = with self; [];
667 src = fetchurl {
667 src = fetchurl {
668 url = "https://pypi.python.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz";
668 url = "https://pypi.python.org/packages/45/a0/317c6422b26c12fe0161e936fc35f36552069ba8e6f7ecbd99bbffe32a5f/meld3-1.0.2.tar.gz";
669 md5 = "3ccc78cd79cffd63a751ad7684c02c91";
669 md5 = "3ccc78cd79cffd63a751ad7684c02c91";
670 };
670 };
671 };
671 };
672 mock = super.buildPythonPackage {
672 mock = super.buildPythonPackage {
673 name = "mock-1.0.1";
673 name = "mock-1.0.1";
674 buildInputs = with self; [];
674 buildInputs = with self; [];
675 doCheck = false;
675 doCheck = false;
676 propagatedBuildInputs = with self; [];
676 propagatedBuildInputs = with self; [];
677 src = fetchurl {
677 src = fetchurl {
678 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
678 url = "https://pypi.python.org/packages/15/45/30273ee91feb60dabb8fbb2da7868520525f02cf910279b3047182feed80/mock-1.0.1.zip";
679 md5 = "869f08d003c289a97c1a6610faf5e913";
679 md5 = "869f08d003c289a97c1a6610faf5e913";
680 };
680 };
681 };
681 };
682 msgpack-python = super.buildPythonPackage {
682 msgpack-python = super.buildPythonPackage {
683 name = "msgpack-python-0.4.6";
683 name = "msgpack-python-0.4.6";
684 buildInputs = with self; [];
684 buildInputs = with self; [];
685 doCheck = false;
685 doCheck = false;
686 propagatedBuildInputs = with self; [];
686 propagatedBuildInputs = with self; [];
687 src = fetchurl {
687 src = fetchurl {
688 url = "https://pypi.python.org/packages/15/ce/ff2840885789ef8035f66cd506ea05bdb228340307d5e71a7b1e3f82224c/msgpack-python-0.4.6.tar.gz";
688 url = "https://pypi.python.org/packages/15/ce/ff2840885789ef8035f66cd506ea05bdb228340307d5e71a7b1e3f82224c/msgpack-python-0.4.6.tar.gz";
689 md5 = "8b317669314cf1bc881716cccdaccb30";
689 md5 = "8b317669314cf1bc881716cccdaccb30";
690 };
690 };
691 };
691 };
692 nose = super.buildPythonPackage {
692 nose = super.buildPythonPackage {
693 name = "nose-1.3.6";
693 name = "nose-1.3.6";
694 buildInputs = with self; [];
694 buildInputs = with self; [];
695 doCheck = false;
695 doCheck = false;
696 propagatedBuildInputs = with self; [];
696 propagatedBuildInputs = with self; [];
697 src = fetchurl {
697 src = fetchurl {
698 url = "https://pypi.python.org/packages/70/c7/469e68148d17a0d3db5ed49150242fd70a74a8147b8f3f8b87776e028d99/nose-1.3.6.tar.gz";
698 url = "https://pypi.python.org/packages/70/c7/469e68148d17a0d3db5ed49150242fd70a74a8147b8f3f8b87776e028d99/nose-1.3.6.tar.gz";
699 md5 = "0ca546d81ca8309080fc80cb389e7a16";
699 md5 = "0ca546d81ca8309080fc80cb389e7a16";
700 };
700 };
701 };
701 };
702 objgraph = super.buildPythonPackage {
702 objgraph = super.buildPythonPackage {
703 name = "objgraph-2.0.0";
703 name = "objgraph-2.0.0";
704 buildInputs = with self; [];
704 buildInputs = with self; [];
705 doCheck = false;
705 doCheck = false;
706 propagatedBuildInputs = with self; [];
706 propagatedBuildInputs = with self; [];
707 src = fetchurl {
707 src = fetchurl {
708 url = "https://pypi.python.org/packages/d7/33/ace750b59247496ed769b170586c5def7202683f3d98e737b75b767ff29e/objgraph-2.0.0.tar.gz";
708 url = "https://pypi.python.org/packages/d7/33/ace750b59247496ed769b170586c5def7202683f3d98e737b75b767ff29e/objgraph-2.0.0.tar.gz";
709 md5 = "25b0d5e5adc74aa63ead15699614159c";
709 md5 = "25b0d5e5adc74aa63ead15699614159c";
710 };
710 };
711 };
711 };
712 packaging = super.buildPythonPackage {
712 packaging = super.buildPythonPackage {
713 name = "packaging-15.2";
713 name = "packaging-15.2";
714 buildInputs = with self; [];
714 buildInputs = with self; [];
715 doCheck = false;
715 doCheck = false;
716 propagatedBuildInputs = with self; [];
716 propagatedBuildInputs = with self; [];
717 src = fetchurl {
717 src = fetchurl {
718 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
718 url = "https://pypi.python.org/packages/24/c4/185da1304f07047dc9e0c46c31db75c0351bd73458ac3efad7da3dbcfbe1/packaging-15.2.tar.gz";
719 md5 = "c16093476f6ced42128bf610e5db3784";
719 md5 = "c16093476f6ced42128bf610e5db3784";
720 };
720 };
721 };
721 };
722 paramiko = super.buildPythonPackage {
722 paramiko = super.buildPythonPackage {
723 name = "paramiko-1.15.1";
723 name = "paramiko-1.15.1";
724 buildInputs = with self; [];
724 buildInputs = with self; [];
725 doCheck = false;
725 doCheck = false;
726 propagatedBuildInputs = with self; [pycrypto ecdsa];
726 propagatedBuildInputs = with self; [pycrypto ecdsa];
727 src = fetchurl {
727 src = fetchurl {
728 url = "https://pypi.python.org/packages/04/2b/a22d2a560c1951abbbf95a0628e245945565f70dc082d9e784666887222c/paramiko-1.15.1.tar.gz";
728 url = "https://pypi.python.org/packages/04/2b/a22d2a560c1951abbbf95a0628e245945565f70dc082d9e784666887222c/paramiko-1.15.1.tar.gz";
729 md5 = "48c274c3f9b1282932567b21f6acf3b5";
729 md5 = "48c274c3f9b1282932567b21f6acf3b5";
730 };
730 };
731 };
731 };
732 pep8 = super.buildPythonPackage {
732 pep8 = super.buildPythonPackage {
733 name = "pep8-1.5.7";
733 name = "pep8-1.5.7";
734 buildInputs = with self; [];
734 buildInputs = with self; [];
735 doCheck = false;
735 doCheck = false;
736 propagatedBuildInputs = with self; [];
736 propagatedBuildInputs = with self; [];
737 src = fetchurl {
737 src = fetchurl {
738 url = "https://pypi.python.org/packages/8b/de/259f5e735897ada1683489dd514b2a1c91aaa74e5e6b68f80acf128a6368/pep8-1.5.7.tar.gz";
738 url = "https://pypi.python.org/packages/8b/de/259f5e735897ada1683489dd514b2a1c91aaa74e5e6b68f80acf128a6368/pep8-1.5.7.tar.gz";
739 md5 = "f6adbdd69365ecca20513c709f9b7c93";
739 md5 = "f6adbdd69365ecca20513c709f9b7c93";
740 };
740 };
741 };
741 };
742 psutil = super.buildPythonPackage {
742 psutil = super.buildPythonPackage {
743 name = "psutil-2.2.1";
743 name = "psutil-2.2.1";
744 buildInputs = with self; [];
744 buildInputs = with self; [];
745 doCheck = false;
745 doCheck = false;
746 propagatedBuildInputs = with self; [];
746 propagatedBuildInputs = with self; [];
747 src = fetchurl {
747 src = fetchurl {
748 url = "https://pypi.python.org/packages/df/47/ee54ef14dd40f8ce831a7581001a5096494dc99fe71586260ca6b531fe86/psutil-2.2.1.tar.gz";
748 url = "https://pypi.python.org/packages/df/47/ee54ef14dd40f8ce831a7581001a5096494dc99fe71586260ca6b531fe86/psutil-2.2.1.tar.gz";
749 md5 = "1a2b58cd9e3a53528bb6148f0c4d5244";
749 md5 = "1a2b58cd9e3a53528bb6148f0c4d5244";
750 };
750 };
751 };
751 };
752 psycopg2 = super.buildPythonPackage {
752 psycopg2 = super.buildPythonPackage {
753 name = "psycopg2-2.6";
753 name = "psycopg2-2.6";
754 buildInputs = with self; [];
754 buildInputs = with self; [];
755 doCheck = false;
755 doCheck = false;
756 propagatedBuildInputs = with self; [];
756 propagatedBuildInputs = with self; [];
757 src = fetchurl {
757 src = fetchurl {
758 url = "https://pypi.python.org/packages/dd/c7/9016ff8ff69da269b1848276eebfb264af5badf6b38caad805426771f04d/psycopg2-2.6.tar.gz";
758 url = "https://pypi.python.org/packages/dd/c7/9016ff8ff69da269b1848276eebfb264af5badf6b38caad805426771f04d/psycopg2-2.6.tar.gz";
759 md5 = "fbbb039a8765d561a1c04969bbae7c74";
759 md5 = "fbbb039a8765d561a1c04969bbae7c74";
760 };
760 };
761 };
761 };
762 py = super.buildPythonPackage {
762 py = super.buildPythonPackage {
763 name = "py-1.4.29";
763 name = "py-1.4.29";
764 buildInputs = with self; [];
764 buildInputs = with self; [];
765 doCheck = false;
765 doCheck = false;
766 propagatedBuildInputs = with self; [];
766 propagatedBuildInputs = with self; [];
767 src = fetchurl {
767 src = fetchurl {
768 url = "https://pypi.python.org/packages/2a/bc/a1a4a332ac10069b8e5e25136a35e08a03f01fd6ab03d819889d79a1fd65/py-1.4.29.tar.gz";
768 url = "https://pypi.python.org/packages/2a/bc/a1a4a332ac10069b8e5e25136a35e08a03f01fd6ab03d819889d79a1fd65/py-1.4.29.tar.gz";
769 md5 = "c28e0accba523a29b35a48bb703fb96c";
769 md5 = "c28e0accba523a29b35a48bb703fb96c";
770 };
770 };
771 };
771 };
772 py-bcrypt = super.buildPythonPackage {
772 py-bcrypt = super.buildPythonPackage {
773 name = "py-bcrypt-0.4";
773 name = "py-bcrypt-0.4";
774 buildInputs = with self; [];
774 buildInputs = with self; [];
775 doCheck = false;
775 doCheck = false;
776 propagatedBuildInputs = with self; [];
776 propagatedBuildInputs = with self; [];
777 src = fetchurl {
777 src = fetchurl {
778 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
778 url = "https://pypi.python.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
779 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
779 md5 = "dd8b367d6b716a2ea2e72392525f4e36";
780 };
780 };
781 };
781 };
782 pycrypto = super.buildPythonPackage {
782 pycrypto = super.buildPythonPackage {
783 name = "pycrypto-2.6.1";
783 name = "pycrypto-2.6.1";
784 buildInputs = with self; [];
784 buildInputs = with self; [];
785 doCheck = false;
785 doCheck = false;
786 propagatedBuildInputs = with self; [];
786 propagatedBuildInputs = with self; [];
787 src = fetchurl {
787 src = fetchurl {
788 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
788 url = "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
789 md5 = "55a61a054aa66812daf5161a0d5d7eda";
789 md5 = "55a61a054aa66812daf5161a0d5d7eda";
790 };
790 };
791 };
791 };
792 pycurl = super.buildPythonPackage {
792 pycurl = super.buildPythonPackage {
793 name = "pycurl-7.19.5";
793 name = "pycurl-7.19.5";
794 buildInputs = with self; [];
794 buildInputs = with self; [];
795 doCheck = false;
795 doCheck = false;
796 propagatedBuildInputs = with self; [];
796 propagatedBuildInputs = with self; [];
797 src = fetchurl {
797 src = fetchurl {
798 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
798 url = "https://pypi.python.org/packages/6c/48/13bad289ef6f4869b1d8fc11ae54de8cfb3cc4a2eb9f7419c506f763be46/pycurl-7.19.5.tar.gz";
799 md5 = "47b4eac84118e2606658122104e62072";
799 md5 = "47b4eac84118e2606658122104e62072";
800 };
800 };
801 };
801 };
802 pyelasticsearch = super.buildPythonPackage {
803 name = "pyelasticsearch-1.4";
804 buildInputs = with self; [];
805 doCheck = false;
806 propagatedBuildInputs = with self; [certifi elasticsearch urllib3 simplejson six];
807 src = fetchurl {
808 url = "https://pypi.python.org/packages/2f/3a/7643cfcfc4cbdbb20ada800bbd54ac9705d0c047d7b8f8d5eeeb3047b4eb/pyelasticsearch-1.4.tar.gz";
809 md5 = "ed61ebb7b253364e55b4923d11e17049";
810 };
811 };
812 pyflakes = super.buildPythonPackage {
802 pyflakes = super.buildPythonPackage {
813 name = "pyflakes-0.8.1";
803 name = "pyflakes-0.8.1";
814 buildInputs = with self; [];
804 buildInputs = with self; [];
815 doCheck = false;
805 doCheck = false;
816 propagatedBuildInputs = with self; [];
806 propagatedBuildInputs = with self; [];
817 src = fetchurl {
807 src = fetchurl {
818 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
808 url = "https://pypi.python.org/packages/75/22/a90ec0252f4f87f3ffb6336504de71fe16a49d69c4538dae2f12b9360a38/pyflakes-0.8.1.tar.gz";
819 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
809 md5 = "905fe91ad14b912807e8fdc2ac2e2c23";
820 };
810 };
821 };
811 };
822 pyparsing = super.buildPythonPackage {
812 pyparsing = super.buildPythonPackage {
823 name = "pyparsing-1.5.7";
813 name = "pyparsing-1.5.7";
824 buildInputs = with self; [];
814 buildInputs = with self; [];
825 doCheck = false;
815 doCheck = false;
826 propagatedBuildInputs = with self; [];
816 propagatedBuildInputs = with self; [];
827 src = fetchurl {
817 src = fetchurl {
828 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
818 url = "https://pypi.python.org/packages/2e/26/e8fb5b4256a5f5036be7ce115ef8db8d06bc537becfbdc46c6af008314ee/pyparsing-1.5.7.zip";
829 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
819 md5 = "b86854857a368d6ccb4d5b6e76d0637f";
830 };
820 };
831 };
821 };
832 pyramid = super.buildPythonPackage {
822 pyramid = super.buildPythonPackage {
833 name = "pyramid-1.6.1";
823 name = "pyramid-1.6.1";
834 buildInputs = with self; [];
824 buildInputs = with self; [];
835 doCheck = false;
825 doCheck = false;
836 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
826 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
837 src = fetchurl {
827 src = fetchurl {
838 url = "https://pypi.python.org/packages/30/b3/fcc4a2a4800cbf21989e00454b5828cf1f7fe35c63e0810b350e56d4c475/pyramid-1.6.1.tar.gz";
828 url = "https://pypi.python.org/packages/30/b3/fcc4a2a4800cbf21989e00454b5828cf1f7fe35c63e0810b350e56d4c475/pyramid-1.6.1.tar.gz";
839 md5 = "b18688ff3cc33efdbb098a35b45dd122";
829 md5 = "b18688ff3cc33efdbb098a35b45dd122";
840 };
830 };
841 };
831 };
842 pyramid-beaker = super.buildPythonPackage {
832 pyramid-beaker = super.buildPythonPackage {
843 name = "pyramid-beaker-0.8";
833 name = "pyramid-beaker-0.8";
844 buildInputs = with self; [];
834 buildInputs = with self; [];
845 doCheck = false;
835 doCheck = false;
846 propagatedBuildInputs = with self; [pyramid Beaker];
836 propagatedBuildInputs = with self; [pyramid Beaker];
847 src = fetchurl {
837 src = fetchurl {
848 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
838 url = "https://pypi.python.org/packages/d9/6e/b85426e00fd3d57f4545f74e1c3828552d8700f13ededeef9233f7bca8be/pyramid_beaker-0.8.tar.gz";
849 md5 = "22f14be31b06549f80890e2c63a93834";
839 md5 = "22f14be31b06549f80890e2c63a93834";
850 };
840 };
851 };
841 };
852 pyramid-debugtoolbar = super.buildPythonPackage {
842 pyramid-debugtoolbar = super.buildPythonPackage {
853 name = "pyramid-debugtoolbar-2.4.2";
843 name = "pyramid-debugtoolbar-2.4.2";
854 buildInputs = with self; [];
844 buildInputs = with self; [];
855 doCheck = false;
845 doCheck = false;
856 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
846 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
857 src = fetchurl {
847 src = fetchurl {
858 url = "https://pypi.python.org/packages/89/00/ed5426ee41ed747ba3ffd30e8230841a6878286ea67d480b1444d24f06a2/pyramid_debugtoolbar-2.4.2.tar.gz";
848 url = "https://pypi.python.org/packages/89/00/ed5426ee41ed747ba3ffd30e8230841a6878286ea67d480b1444d24f06a2/pyramid_debugtoolbar-2.4.2.tar.gz";
859 md5 = "073ea67086cc4bd5decc3a000853642d";
849 md5 = "073ea67086cc4bd5decc3a000853642d";
860 };
850 };
861 };
851 };
862 pyramid-jinja2 = super.buildPythonPackage {
852 pyramid-jinja2 = super.buildPythonPackage {
863 name = "pyramid-jinja2-2.5";
853 name = "pyramid-jinja2-2.5";
864 buildInputs = with self; [];
854 buildInputs = with self; [];
865 doCheck = false;
855 doCheck = false;
866 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
856 propagatedBuildInputs = with self; [pyramid zope.deprecation Jinja2 MarkupSafe];
867 src = fetchurl {
857 src = fetchurl {
868 url = "https://pypi.python.org/packages/a1/80/595e26ffab7deba7208676b6936b7e5a721875710f982e59899013cae1ed/pyramid_jinja2-2.5.tar.gz";
858 url = "https://pypi.python.org/packages/a1/80/595e26ffab7deba7208676b6936b7e5a721875710f982e59899013cae1ed/pyramid_jinja2-2.5.tar.gz";
869 md5 = "07cb6547204ac5e6f0b22a954ccee928";
859 md5 = "07cb6547204ac5e6f0b22a954ccee928";
870 };
860 };
871 };
861 };
872 pyramid-mako = super.buildPythonPackage {
862 pyramid-mako = super.buildPythonPackage {
873 name = "pyramid-mako-1.0.2";
863 name = "pyramid-mako-1.0.2";
874 buildInputs = with self; [];
864 buildInputs = with self; [];
875 doCheck = false;
865 doCheck = false;
876 propagatedBuildInputs = with self; [pyramid Mako];
866 propagatedBuildInputs = with self; [pyramid Mako];
877 src = fetchurl {
867 src = fetchurl {
878 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
868 url = "https://pypi.python.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
879 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
869 md5 = "ee25343a97eb76bd90abdc2a774eb48a";
880 };
870 };
881 };
871 };
882 pysqlite = super.buildPythonPackage {
872 pysqlite = super.buildPythonPackage {
883 name = "pysqlite-2.6.3";
873 name = "pysqlite-2.6.3";
884 buildInputs = with self; [];
874 buildInputs = with self; [];
885 doCheck = false;
875 doCheck = false;
886 propagatedBuildInputs = with self; [];
876 propagatedBuildInputs = with self; [];
887 src = fetchurl {
877 src = fetchurl {
888 url = "https://pypi.python.org/packages/5c/a6/1c429cd4c8069cf4bfbd0eb4d592b3f4042155a8202df83d7e9b93aa3dc2/pysqlite-2.6.3.tar.gz";
878 url = "https://pypi.python.org/packages/5c/a6/1c429cd4c8069cf4bfbd0eb4d592b3f4042155a8202df83d7e9b93aa3dc2/pysqlite-2.6.3.tar.gz";
889 md5 = "7ff1cedee74646b50117acff87aa1cfa";
879 md5 = "7ff1cedee74646b50117acff87aa1cfa";
890 };
880 };
891 };
881 };
892 pytest = super.buildPythonPackage {
882 pytest = super.buildPythonPackage {
893 name = "pytest-2.8.5";
883 name = "pytest-2.8.5";
894 buildInputs = with self; [];
884 buildInputs = with self; [];
895 doCheck = false;
885 doCheck = false;
896 propagatedBuildInputs = with self; [py];
886 propagatedBuildInputs = with self; [py];
897 src = fetchurl {
887 src = fetchurl {
898 url = "https://pypi.python.org/packages/b1/3d/d7ea9b0c51e0cacded856e49859f0a13452747491e842c236bbab3714afe/pytest-2.8.5.zip";
888 url = "https://pypi.python.org/packages/b1/3d/d7ea9b0c51e0cacded856e49859f0a13452747491e842c236bbab3714afe/pytest-2.8.5.zip";
899 md5 = "8493b06f700862f1294298d6c1b715a9";
889 md5 = "8493b06f700862f1294298d6c1b715a9";
900 };
890 };
901 };
891 };
902 pytest-catchlog = super.buildPythonPackage {
892 pytest-catchlog = super.buildPythonPackage {
903 name = "pytest-catchlog-1.2.2";
893 name = "pytest-catchlog-1.2.2";
904 buildInputs = with self; [];
894 buildInputs = with self; [];
905 doCheck = false;
895 doCheck = false;
906 propagatedBuildInputs = with self; [py pytest];
896 propagatedBuildInputs = with self; [py pytest];
907 src = fetchurl {
897 src = fetchurl {
908 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
898 url = "https://pypi.python.org/packages/f2/2b/2faccdb1a978fab9dd0bf31cca9f6847fbe9184a0bdcc3011ac41dd44191/pytest-catchlog-1.2.2.zip";
909 md5 = "09d890c54c7456c818102b7ff8c182c8";
899 md5 = "09d890c54c7456c818102b7ff8c182c8";
910 };
900 };
911 };
901 };
912 pytest-cov = super.buildPythonPackage {
902 pytest-cov = super.buildPythonPackage {
913 name = "pytest-cov-1.8.1";
903 name = "pytest-cov-1.8.1";
914 buildInputs = with self; [];
904 buildInputs = with self; [];
915 doCheck = false;
905 doCheck = false;
916 propagatedBuildInputs = with self; [py pytest coverage cov-core];
906 propagatedBuildInputs = with self; [py pytest coverage cov-core];
917 src = fetchurl {
907 src = fetchurl {
918 url = "https://pypi.python.org/packages/11/4b/b04646e97f1721878eb21e9f779102d84dd044d324382263b1770a3e4838/pytest-cov-1.8.1.tar.gz";
908 url = "https://pypi.python.org/packages/11/4b/b04646e97f1721878eb21e9f779102d84dd044d324382263b1770a3e4838/pytest-cov-1.8.1.tar.gz";
919 md5 = "76c778afa2494088270348be42d759fc";
909 md5 = "76c778afa2494088270348be42d759fc";
920 };
910 };
921 };
911 };
922 pytest-profiling = super.buildPythonPackage {
912 pytest-profiling = super.buildPythonPackage {
923 name = "pytest-profiling-1.0.1";
913 name = "pytest-profiling-1.0.1";
924 buildInputs = with self; [];
914 buildInputs = with self; [];
925 doCheck = false;
915 doCheck = false;
926 propagatedBuildInputs = with self; [six pytest gprof2dot];
916 propagatedBuildInputs = with self; [six pytest gprof2dot];
927 src = fetchurl {
917 src = fetchurl {
928 url = "https://pypi.python.org/packages/d8/67/8ffab73406e22870e07fa4dc8dce1d7689b26dba8efd00161c9b6fc01ec0/pytest-profiling-1.0.1.tar.gz";
918 url = "https://pypi.python.org/packages/d8/67/8ffab73406e22870e07fa4dc8dce1d7689b26dba8efd00161c9b6fc01ec0/pytest-profiling-1.0.1.tar.gz";
929 md5 = "354404eb5b3fd4dc5eb7fffbb3d9b68b";
919 md5 = "354404eb5b3fd4dc5eb7fffbb3d9b68b";
930 };
920 };
931 };
921 };
932 pytest-runner = super.buildPythonPackage {
922 pytest-runner = super.buildPythonPackage {
933 name = "pytest-runner-2.7.1";
923 name = "pytest-runner-2.7.1";
934 buildInputs = with self; [];
924 buildInputs = with self; [];
935 doCheck = false;
925 doCheck = false;
936 propagatedBuildInputs = with self; [];
926 propagatedBuildInputs = with self; [];
937 src = fetchurl {
927 src = fetchurl {
938 url = "https://pypi.python.org/packages/99/6b/c4ff4418d3424d4475b7af60724fd4a5cdd91ed8e489dc9443281f0052bc/pytest-runner-2.7.1.tar.gz";
928 url = "https://pypi.python.org/packages/99/6b/c4ff4418d3424d4475b7af60724fd4a5cdd91ed8e489dc9443281f0052bc/pytest-runner-2.7.1.tar.gz";
939 md5 = "e56f0bc8d79a6bd91772b44ef4215c7e";
929 md5 = "e56f0bc8d79a6bd91772b44ef4215c7e";
940 };
930 };
941 };
931 };
942 pytest-timeout = super.buildPythonPackage {
932 pytest-timeout = super.buildPythonPackage {
943 name = "pytest-timeout-0.4";
933 name = "pytest-timeout-0.4";
944 buildInputs = with self; [];
934 buildInputs = with self; [];
945 doCheck = false;
935 doCheck = false;
946 propagatedBuildInputs = with self; [pytest];
936 propagatedBuildInputs = with self; [pytest];
947 src = fetchurl {
937 src = fetchurl {
948 url = "https://pypi.python.org/packages/24/48/5f6bd4b8026a26e1dd427243d560a29a0f1b24a5c7cffca4bf049a7bb65b/pytest-timeout-0.4.tar.gz";
938 url = "https://pypi.python.org/packages/24/48/5f6bd4b8026a26e1dd427243d560a29a0f1b24a5c7cffca4bf049a7bb65b/pytest-timeout-0.4.tar.gz";
949 md5 = "03b28aff69cbbfb959ed35ade5fde262";
939 md5 = "03b28aff69cbbfb959ed35ade5fde262";
950 };
940 };
951 };
941 };
952 python-dateutil = super.buildPythonPackage {
942 python-dateutil = super.buildPythonPackage {
953 name = "python-dateutil-1.5";
943 name = "python-dateutil-1.5";
954 buildInputs = with self; [];
944 buildInputs = with self; [];
955 doCheck = false;
945 doCheck = false;
956 propagatedBuildInputs = with self; [];
946 propagatedBuildInputs = with self; [];
957 src = fetchurl {
947 src = fetchurl {
958 url = "https://pypi.python.org/packages/b4/7c/df59c89a753eb33c7c44e1dd42de0e9bc2ccdd5a4d576e0bfad97cc280cb/python-dateutil-1.5.tar.gz";
948 url = "https://pypi.python.org/packages/b4/7c/df59c89a753eb33c7c44e1dd42de0e9bc2ccdd5a4d576e0bfad97cc280cb/python-dateutil-1.5.tar.gz";
959 md5 = "0dcb1de5e5cad69490a3b6ab63f0cfa5";
949 md5 = "0dcb1de5e5cad69490a3b6ab63f0cfa5";
960 };
950 };
961 };
951 };
962 python-editor = super.buildPythonPackage {
952 python-editor = super.buildPythonPackage {
963 name = "python-editor-1.0";
953 name = "python-editor-1.0";
964 buildInputs = with self; [];
954 buildInputs = with self; [];
965 doCheck = false;
955 doCheck = false;
966 propagatedBuildInputs = with self; [];
956 propagatedBuildInputs = with self; [];
967 src = fetchurl {
957 src = fetchurl {
968 url = "https://pypi.python.org/packages/f5/d9/01eb441489c8bd2adb33ee4f3aea299a3db531a584cb39c57a0ecf516d9c/python-editor-1.0.tar.gz";
958 url = "https://pypi.python.org/packages/f5/d9/01eb441489c8bd2adb33ee4f3aea299a3db531a584cb39c57a0ecf516d9c/python-editor-1.0.tar.gz";
969 md5 = "a5ead611360b17b52507297d8590b4e8";
959 md5 = "a5ead611360b17b52507297d8590b4e8";
970 };
960 };
971 };
961 };
972 python-ldap = super.buildPythonPackage {
962 python-ldap = super.buildPythonPackage {
973 name = "python-ldap-2.4.19";
963 name = "python-ldap-2.4.19";
974 buildInputs = with self; [];
964 buildInputs = with self; [];
975 doCheck = false;
965 doCheck = false;
976 propagatedBuildInputs = with self; [setuptools];
966 propagatedBuildInputs = with self; [setuptools];
977 src = fetchurl {
967 src = fetchurl {
978 url = "https://pypi.python.org/packages/42/81/1b64838c82e64f14d4e246ff00b52e650a35c012551b891ada2b85d40737/python-ldap-2.4.19.tar.gz";
968 url = "https://pypi.python.org/packages/42/81/1b64838c82e64f14d4e246ff00b52e650a35c012551b891ada2b85d40737/python-ldap-2.4.19.tar.gz";
979 md5 = "b941bf31d09739492aa19ef679e94ae3";
969 md5 = "b941bf31d09739492aa19ef679e94ae3";
980 };
970 };
981 };
971 };
982 python-memcached = super.buildPythonPackage {
972 python-memcached = super.buildPythonPackage {
983 name = "python-memcached-1.57";
973 name = "python-memcached-1.57";
984 buildInputs = with self; [];
974 buildInputs = with self; [];
985 doCheck = false;
975 doCheck = false;
986 propagatedBuildInputs = with self; [six];
976 propagatedBuildInputs = with self; [six];
987 src = fetchurl {
977 src = fetchurl {
988 url = "https://pypi.python.org/packages/52/9d/eebc0dcbc5c7c66840ad207dfc1baa376dadb74912484bff73819cce01e6/python-memcached-1.57.tar.gz";
978 url = "https://pypi.python.org/packages/52/9d/eebc0dcbc5c7c66840ad207dfc1baa376dadb74912484bff73819cce01e6/python-memcached-1.57.tar.gz";
989 md5 = "de21f64b42b2d961f3d4ad7beb5468a1";
979 md5 = "de21f64b42b2d961f3d4ad7beb5468a1";
990 };
980 };
991 };
981 };
992 python-pam = super.buildPythonPackage {
982 python-pam = super.buildPythonPackage {
993 name = "python-pam-1.8.2";
983 name = "python-pam-1.8.2";
994 buildInputs = with self; [];
984 buildInputs = with self; [];
995 doCheck = false;
985 doCheck = false;
996 propagatedBuildInputs = with self; [];
986 propagatedBuildInputs = with self; [];
997 src = fetchurl {
987 src = fetchurl {
998 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
988 url = "https://pypi.python.org/packages/de/8c/f8f5d38b4f26893af267ea0b39023d4951705ab0413a39e0cf7cf4900505/python-pam-1.8.2.tar.gz";
999 md5 = "db71b6b999246fb05d78ecfbe166629d";
989 md5 = "db71b6b999246fb05d78ecfbe166629d";
1000 };
990 };
1001 };
991 };
1002 pytz = super.buildPythonPackage {
992 pytz = super.buildPythonPackage {
1003 name = "pytz-2015.4";
993 name = "pytz-2015.4";
1004 buildInputs = with self; [];
994 buildInputs = with self; [];
1005 doCheck = false;
995 doCheck = false;
1006 propagatedBuildInputs = with self; [];
996 propagatedBuildInputs = with self; [];
1007 src = fetchurl {
997 src = fetchurl {
1008 url = "https://pypi.python.org/packages/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
998 url = "https://pypi.python.org/packages/7e/1a/f43b5c92df7b156822030fed151327ea096bcf417e45acc23bd1df43472f/pytz-2015.4.zip";
1009 md5 = "233f2a2b370d03f9b5911700cc9ebf3c";
999 md5 = "233f2a2b370d03f9b5911700cc9ebf3c";
1010 };
1000 };
1011 };
1001 };
1012 pyzmq = super.buildPythonPackage {
1002 pyzmq = super.buildPythonPackage {
1013 name = "pyzmq-14.6.0";
1003 name = "pyzmq-14.6.0";
1014 buildInputs = with self; [];
1004 buildInputs = with self; [];
1015 doCheck = false;
1005 doCheck = false;
1016 propagatedBuildInputs = with self; [];
1006 propagatedBuildInputs = with self; [];
1017 src = fetchurl {
1007 src = fetchurl {
1018 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1008 url = "https://pypi.python.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1019 md5 = "395b5de95a931afa5b14c9349a5b8024";
1009 md5 = "395b5de95a931afa5b14c9349a5b8024";
1020 };
1010 };
1021 };
1011 };
1022 recaptcha-client = super.buildPythonPackage {
1012 recaptcha-client = super.buildPythonPackage {
1023 name = "recaptcha-client-1.0.6";
1013 name = "recaptcha-client-1.0.6";
1024 buildInputs = with self; [];
1014 buildInputs = with self; [];
1025 doCheck = false;
1015 doCheck = false;
1026 propagatedBuildInputs = with self; [];
1016 propagatedBuildInputs = with self; [];
1027 src = fetchurl {
1017 src = fetchurl {
1028 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1018 url = "https://pypi.python.org/packages/0a/ea/5f2fbbfd894bdac1c68ef8d92019066cfcf9fbff5fe3d728d2b5c25c8db4/recaptcha-client-1.0.6.tar.gz";
1029 md5 = "74228180f7e1fb76c4d7089160b0d919";
1019 md5 = "74228180f7e1fb76c4d7089160b0d919";
1030 };
1020 };
1031 };
1021 };
1032 repoze.lru = super.buildPythonPackage {
1022 repoze.lru = super.buildPythonPackage {
1033 name = "repoze.lru-0.6";
1023 name = "repoze.lru-0.6";
1034 buildInputs = with self; [];
1024 buildInputs = with self; [];
1035 doCheck = false;
1025 doCheck = false;
1036 propagatedBuildInputs = with self; [];
1026 propagatedBuildInputs = with self; [];
1037 src = fetchurl {
1027 src = fetchurl {
1038 url = "https://pypi.python.org/packages/6e/1e/aa15cc90217e086dc8769872c8778b409812ff036bf021b15795638939e4/repoze.lru-0.6.tar.gz";
1028 url = "https://pypi.python.org/packages/6e/1e/aa15cc90217e086dc8769872c8778b409812ff036bf021b15795638939e4/repoze.lru-0.6.tar.gz";
1039 md5 = "2c3b64b17a8e18b405f55d46173e14dd";
1029 md5 = "2c3b64b17a8e18b405f55d46173e14dd";
1040 };
1030 };
1041 };
1031 };
1042 requests = super.buildPythonPackage {
1032 requests = super.buildPythonPackage {
1043 name = "requests-2.9.1";
1033 name = "requests-2.9.1";
1044 buildInputs = with self; [];
1034 buildInputs = with self; [];
1045 doCheck = false;
1035 doCheck = false;
1046 propagatedBuildInputs = with self; [];
1036 propagatedBuildInputs = with self; [];
1047 src = fetchurl {
1037 src = fetchurl {
1048 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1038 url = "https://pypi.python.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1049 md5 = "0b7f480d19012ec52bab78292efd976d";
1039 md5 = "0b7f480d19012ec52bab78292efd976d";
1050 };
1040 };
1051 };
1041 };
1052 rhodecode-enterprise-ce = super.buildPythonPackage {
1042 rhodecode-enterprise-ce = super.buildPythonPackage {
1053 name = "rhodecode-enterprise-ce-4.0.1";
1043 name = "rhodecode-enterprise-ce-4.1.0";
1054 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1044 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1055 doCheck = true;
1045 doCheck = true;
1056 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors psutil py-bcrypt];
1046 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors psutil py-bcrypt];
1057 src = ./.;
1047 src = ./.;
1058 };
1048 };
1059 rhodecode-tools = super.buildPythonPackage {
1049 rhodecode-tools = super.buildPythonPackage {
1060 name = "rhodecode-tools-0.7.1";
1050 name = "rhodecode-tools-0.8.3";
1061 buildInputs = with self; [];
1051 buildInputs = with self; [];
1062 doCheck = false;
1052 doCheck = false;
1063 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests Whoosh pyelasticsearch];
1053 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests Whoosh elasticsearch elasticsearch-dsl];
1064 src = fetchurl {
1054 src = fetchurl {
1065 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.7.1.zip";
1055 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.8.3.zip";
1066 md5 = "91daea803aaa264ce7a8213bc2220d4c";
1056 md5 = "9acdfd71b8ddf4056057065f37ab9ccb";
1067 };
1057 };
1068 };
1058 };
1069 serpent = super.buildPythonPackage {
1059 serpent = super.buildPythonPackage {
1070 name = "serpent-1.12";
1060 name = "serpent-1.12";
1071 buildInputs = with self; [];
1061 buildInputs = with self; [];
1072 doCheck = false;
1062 doCheck = false;
1073 propagatedBuildInputs = with self; [];
1063 propagatedBuildInputs = with self; [];
1074 src = fetchurl {
1064 src = fetchurl {
1075 url = "https://pypi.python.org/packages/3b/19/1e0e83b47c09edaef8398655088036e7e67386b5c48770218ebb339fbbd5/serpent-1.12.tar.gz";
1065 url = "https://pypi.python.org/packages/3b/19/1e0e83b47c09edaef8398655088036e7e67386b5c48770218ebb339fbbd5/serpent-1.12.tar.gz";
1076 md5 = "05869ac7b062828b34f8f927f0457b65";
1066 md5 = "05869ac7b062828b34f8f927f0457b65";
1077 };
1067 };
1078 };
1068 };
1079 setproctitle = super.buildPythonPackage {
1069 setproctitle = super.buildPythonPackage {
1080 name = "setproctitle-1.1.8";
1070 name = "setproctitle-1.1.8";
1081 buildInputs = with self; [];
1071 buildInputs = with self; [];
1082 doCheck = false;
1072 doCheck = false;
1083 propagatedBuildInputs = with self; [];
1073 propagatedBuildInputs = with self; [];
1084 src = fetchurl {
1074 src = fetchurl {
1085 url = "https://pypi.python.org/packages/33/c3/ad367a4f4f1ca90468863ae727ac62f6edb558fc09a003d344a02cfc6ea6/setproctitle-1.1.8.tar.gz";
1075 url = "https://pypi.python.org/packages/33/c3/ad367a4f4f1ca90468863ae727ac62f6edb558fc09a003d344a02cfc6ea6/setproctitle-1.1.8.tar.gz";
1086 md5 = "728f4c8c6031bbe56083a48594027edd";
1076 md5 = "728f4c8c6031bbe56083a48594027edd";
1087 };
1077 };
1088 };
1078 };
1089 setuptools = super.buildPythonPackage {
1079 setuptools = super.buildPythonPackage {
1090 name = "setuptools-20.8.1";
1080 name = "setuptools-20.8.1";
1091 buildInputs = with self; [];
1081 buildInputs = with self; [];
1092 doCheck = false;
1082 doCheck = false;
1093 propagatedBuildInputs = with self; [];
1083 propagatedBuildInputs = with self; [];
1094 src = fetchurl {
1084 src = fetchurl {
1095 url = "https://pypi.python.org/packages/c4/19/c1bdc88b53da654df43770f941079dbab4e4788c2dcb5658fb86259894c7/setuptools-20.8.1.zip";
1085 url = "https://pypi.python.org/packages/c4/19/c1bdc88b53da654df43770f941079dbab4e4788c2dcb5658fb86259894c7/setuptools-20.8.1.zip";
1096 md5 = "fe58a5cac0df20bb83942b252a4b0543";
1086 md5 = "fe58a5cac0df20bb83942b252a4b0543";
1097 };
1087 };
1098 };
1088 };
1099 setuptools-scm = super.buildPythonPackage {
1089 setuptools-scm = super.buildPythonPackage {
1100 name = "setuptools-scm-1.11.0";
1090 name = "setuptools-scm-1.11.0";
1101 buildInputs = with self; [];
1091 buildInputs = with self; [];
1102 doCheck = false;
1092 doCheck = false;
1103 propagatedBuildInputs = with self; [];
1093 propagatedBuildInputs = with self; [];
1104 src = fetchurl {
1094 src = fetchurl {
1105 url = "https://pypi.python.org/packages/cd/5f/e3a038292358058d83d764a47d09114aa5a8003ed4529518f9e580f1a94f/setuptools_scm-1.11.0.tar.gz";
1095 url = "https://pypi.python.org/packages/cd/5f/e3a038292358058d83d764a47d09114aa5a8003ed4529518f9e580f1a94f/setuptools_scm-1.11.0.tar.gz";
1106 md5 = "4c5c896ba52e134bbc3507bac6400087";
1096 md5 = "4c5c896ba52e134bbc3507bac6400087";
1107 };
1097 };
1108 };
1098 };
1109 simplejson = super.buildPythonPackage {
1099 simplejson = super.buildPythonPackage {
1110 name = "simplejson-3.7.2";
1100 name = "simplejson-3.7.2";
1111 buildInputs = with self; [];
1101 buildInputs = with self; [];
1112 doCheck = false;
1102 doCheck = false;
1113 propagatedBuildInputs = with self; [];
1103 propagatedBuildInputs = with self; [];
1114 src = fetchurl {
1104 src = fetchurl {
1115 url = "https://pypi.python.org/packages/6d/89/7f13f099344eea9d6722779a1f165087cb559598107844b1ac5dbd831fb1/simplejson-3.7.2.tar.gz";
1105 url = "https://pypi.python.org/packages/6d/89/7f13f099344eea9d6722779a1f165087cb559598107844b1ac5dbd831fb1/simplejson-3.7.2.tar.gz";
1116 md5 = "a5fc7d05d4cb38492285553def5d4b46";
1106 md5 = "a5fc7d05d4cb38492285553def5d4b46";
1117 };
1107 };
1118 };
1108 };
1119 six = super.buildPythonPackage {
1109 six = super.buildPythonPackage {
1120 name = "six-1.9.0";
1110 name = "six-1.9.0";
1121 buildInputs = with self; [];
1111 buildInputs = with self; [];
1122 doCheck = false;
1112 doCheck = false;
1123 propagatedBuildInputs = with self; [];
1113 propagatedBuildInputs = with self; [];
1124 src = fetchurl {
1114 src = fetchurl {
1125 url = "https://pypi.python.org/packages/16/64/1dc5e5976b17466fd7d712e59cbe9fb1e18bec153109e5ba3ed6c9102f1a/six-1.9.0.tar.gz";
1115 url = "https://pypi.python.org/packages/16/64/1dc5e5976b17466fd7d712e59cbe9fb1e18bec153109e5ba3ed6c9102f1a/six-1.9.0.tar.gz";
1126 md5 = "476881ef4012262dfc8adc645ee786c4";
1116 md5 = "476881ef4012262dfc8adc645ee786c4";
1127 };
1117 };
1128 };
1118 };
1129 subprocess32 = super.buildPythonPackage {
1119 subprocess32 = super.buildPythonPackage {
1130 name = "subprocess32-3.2.6";
1120 name = "subprocess32-3.2.6";
1131 buildInputs = with self; [];
1121 buildInputs = with self; [];
1132 doCheck = false;
1122 doCheck = false;
1133 propagatedBuildInputs = with self; [];
1123 propagatedBuildInputs = with self; [];
1134 src = fetchurl {
1124 src = fetchurl {
1135 url = "https://pypi.python.org/packages/28/8d/33ccbff51053f59ae6c357310cac0e79246bbed1d345ecc6188b176d72c3/subprocess32-3.2.6.tar.gz";
1125 url = "https://pypi.python.org/packages/28/8d/33ccbff51053f59ae6c357310cac0e79246bbed1d345ecc6188b176d72c3/subprocess32-3.2.6.tar.gz";
1136 md5 = "754c5ab9f533e764f931136974b618f1";
1126 md5 = "754c5ab9f533e764f931136974b618f1";
1137 };
1127 };
1138 };
1128 };
1139 supervisor = super.buildPythonPackage {
1129 supervisor = super.buildPythonPackage {
1140 name = "supervisor-3.1.3";
1130 name = "supervisor-3.1.3";
1141 buildInputs = with self; [];
1131 buildInputs = with self; [];
1142 doCheck = false;
1132 doCheck = false;
1143 propagatedBuildInputs = with self; [meld3];
1133 propagatedBuildInputs = with self; [meld3];
1144 src = fetchurl {
1134 src = fetchurl {
1145 url = "https://pypi.python.org/packages/a6/41/65ad5bd66230b173eb4d0b8810230f3a9c59ef52ae066e540b6b99895db7/supervisor-3.1.3.tar.gz";
1135 url = "https://pypi.python.org/packages/a6/41/65ad5bd66230b173eb4d0b8810230f3a9c59ef52ae066e540b6b99895db7/supervisor-3.1.3.tar.gz";
1146 md5 = "aad263c4fbc070de63dd354864d5e552";
1136 md5 = "aad263c4fbc070de63dd354864d5e552";
1147 };
1137 };
1148 };
1138 };
1149 transifex-client = super.buildPythonPackage {
1139 transifex-client = super.buildPythonPackage {
1150 name = "transifex-client-0.10";
1140 name = "transifex-client-0.10";
1151 buildInputs = with self; [];
1141 buildInputs = with self; [];
1152 doCheck = false;
1142 doCheck = false;
1153 propagatedBuildInputs = with self; [];
1143 propagatedBuildInputs = with self; [];
1154 src = fetchurl {
1144 src = fetchurl {
1155 url = "https://pypi.python.org/packages/f3/4e/7b925192aee656fb3e04fa6381c8b3dc40198047c3b4a356f6cfd642c809/transifex-client-0.10.tar.gz";
1145 url = "https://pypi.python.org/packages/f3/4e/7b925192aee656fb3e04fa6381c8b3dc40198047c3b4a356f6cfd642c809/transifex-client-0.10.tar.gz";
1156 md5 = "5549538d84b8eede6b254cd81ae024fa";
1146 md5 = "5549538d84b8eede6b254cd81ae024fa";
1157 };
1147 };
1158 };
1148 };
1159 translationstring = super.buildPythonPackage {
1149 translationstring = super.buildPythonPackage {
1160 name = "translationstring-1.3";
1150 name = "translationstring-1.3";
1161 buildInputs = with self; [];
1151 buildInputs = with self; [];
1162 doCheck = false;
1152 doCheck = false;
1163 propagatedBuildInputs = with self; [];
1153 propagatedBuildInputs = with self; [];
1164 src = fetchurl {
1154 src = fetchurl {
1165 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1155 url = "https://pypi.python.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
1166 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1156 md5 = "a4b62e0f3c189c783a1685b3027f7c90";
1167 };
1157 };
1168 };
1158 };
1169 trollius = super.buildPythonPackage {
1159 trollius = super.buildPythonPackage {
1170 name = "trollius-1.0.4";
1160 name = "trollius-1.0.4";
1171 buildInputs = with self; [];
1161 buildInputs = with self; [];
1172 doCheck = false;
1162 doCheck = false;
1173 propagatedBuildInputs = with self; [futures];
1163 propagatedBuildInputs = with self; [futures];
1174 src = fetchurl {
1164 src = fetchurl {
1175 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1165 url = "https://pypi.python.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz";
1176 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1166 md5 = "3631a464d49d0cbfd30ab2918ef2b783";
1177 };
1167 };
1178 };
1168 };
1179 uWSGI = super.buildPythonPackage {
1169 uWSGI = super.buildPythonPackage {
1180 name = "uWSGI-2.0.11.2";
1170 name = "uWSGI-2.0.11.2";
1181 buildInputs = with self; [];
1171 buildInputs = with self; [];
1182 doCheck = false;
1172 doCheck = false;
1183 propagatedBuildInputs = with self; [];
1173 propagatedBuildInputs = with self; [];
1184 src = fetchurl {
1174 src = fetchurl {
1185 url = "https://pypi.python.org/packages/9b/78/918db0cfab0546afa580c1e565209c49aaf1476bbfe491314eadbe47c556/uwsgi-2.0.11.2.tar.gz";
1175 url = "https://pypi.python.org/packages/9b/78/918db0cfab0546afa580c1e565209c49aaf1476bbfe491314eadbe47c556/uwsgi-2.0.11.2.tar.gz";
1186 md5 = "1f02dcbee7f6f61de4b1fd68350cf16f";
1176 md5 = "1f02dcbee7f6f61de4b1fd68350cf16f";
1187 };
1177 };
1188 };
1178 };
1189 urllib3 = super.buildPythonPackage {
1179 urllib3 = super.buildPythonPackage {
1190 name = "urllib3-1.15.1";
1180 name = "urllib3-1.15.1";
1191 buildInputs = with self; [];
1181 buildInputs = with self; [];
1192 doCheck = false;
1182 doCheck = false;
1193 propagatedBuildInputs = with self; [];
1183 propagatedBuildInputs = with self; [];
1194 src = fetchurl {
1184 src = fetchurl {
1195 url = "https://pypi.python.org/packages/49/26/a7d12ea00cb4b9fa1e13b5980e5a04a1fe7c477eb8f657ce0b757a7a497d/urllib3-1.15.1.tar.gz";
1185 url = "https://pypi.python.org/packages/49/26/a7d12ea00cb4b9fa1e13b5980e5a04a1fe7c477eb8f657ce0b757a7a497d/urllib3-1.15.1.tar.gz";
1196 md5 = "5be254b0dbb55d1307ede99e1895c8dd";
1186 md5 = "5be254b0dbb55d1307ede99e1895c8dd";
1197 };
1187 };
1198 };
1188 };
1199 venusian = super.buildPythonPackage {
1189 venusian = super.buildPythonPackage {
1200 name = "venusian-1.0";
1190 name = "venusian-1.0";
1201 buildInputs = with self; [];
1191 buildInputs = with self; [];
1202 doCheck = false;
1192 doCheck = false;
1203 propagatedBuildInputs = with self; [];
1193 propagatedBuildInputs = with self; [];
1204 src = fetchurl {
1194 src = fetchurl {
1205 url = "https://pypi.python.org/packages/86/20/1948e0dfc4930ddde3da8c33612f6a5717c0b4bc28f591a5c5cf014dd390/venusian-1.0.tar.gz";
1195 url = "https://pypi.python.org/packages/86/20/1948e0dfc4930ddde3da8c33612f6a5717c0b4bc28f591a5c5cf014dd390/venusian-1.0.tar.gz";
1206 md5 = "dccf2eafb7113759d60c86faf5538756";
1196 md5 = "dccf2eafb7113759d60c86faf5538756";
1207 };
1197 };
1208 };
1198 };
1209 waitress = super.buildPythonPackage {
1199 waitress = super.buildPythonPackage {
1210 name = "waitress-0.8.9";
1200 name = "waitress-0.8.9";
1211 buildInputs = with self; [];
1201 buildInputs = with self; [];
1212 doCheck = false;
1202 doCheck = false;
1213 propagatedBuildInputs = with self; [setuptools];
1203 propagatedBuildInputs = with self; [setuptools];
1214 src = fetchurl {
1204 src = fetchurl {
1215 url = "https://pypi.python.org/packages/ee/65/fc9dee74a909a1187ca51e4f15ad9c4d35476e4ab5813f73421505c48053/waitress-0.8.9.tar.gz";
1205 url = "https://pypi.python.org/packages/ee/65/fc9dee74a909a1187ca51e4f15ad9c4d35476e4ab5813f73421505c48053/waitress-0.8.9.tar.gz";
1216 md5 = "da3f2e62b3676be5dd630703a68e2a04";
1206 md5 = "da3f2e62b3676be5dd630703a68e2a04";
1217 };
1207 };
1218 };
1208 };
1219 wsgiref = super.buildPythonPackage {
1209 wsgiref = super.buildPythonPackage {
1220 name = "wsgiref-0.1.2";
1210 name = "wsgiref-0.1.2";
1221 buildInputs = with self; [];
1211 buildInputs = with self; [];
1222 doCheck = false;
1212 doCheck = false;
1223 propagatedBuildInputs = with self; [];
1213 propagatedBuildInputs = with self; [];
1224 src = fetchurl {
1214 src = fetchurl {
1225 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1215 url = "https://pypi.python.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
1226 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1216 md5 = "29b146e6ebd0f9fb119fe321f7bcf6cb";
1227 };
1217 };
1228 };
1218 };
1229 zope.cachedescriptors = super.buildPythonPackage {
1219 zope.cachedescriptors = super.buildPythonPackage {
1230 name = "zope.cachedescriptors-4.0.0";
1220 name = "zope.cachedescriptors-4.0.0";
1231 buildInputs = with self; [];
1221 buildInputs = with self; [];
1232 doCheck = false;
1222 doCheck = false;
1233 propagatedBuildInputs = with self; [setuptools];
1223 propagatedBuildInputs = with self; [setuptools];
1234 src = fetchurl {
1224 src = fetchurl {
1235 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
1225 url = "https://pypi.python.org/packages/40/33/694b6644c37f28553f4b9f20b3c3a20fb709a22574dff20b5bdffb09ecd5/zope.cachedescriptors-4.0.0.tar.gz";
1236 md5 = "8d308de8c936792c8e758058fcb7d0f0";
1226 md5 = "8d308de8c936792c8e758058fcb7d0f0";
1237 };
1227 };
1238 };
1228 };
1239 zope.deprecation = super.buildPythonPackage {
1229 zope.deprecation = super.buildPythonPackage {
1240 name = "zope.deprecation-4.1.2";
1230 name = "zope.deprecation-4.1.2";
1241 buildInputs = with self; [];
1231 buildInputs = with self; [];
1242 doCheck = false;
1232 doCheck = false;
1243 propagatedBuildInputs = with self; [setuptools];
1233 propagatedBuildInputs = with self; [setuptools];
1244 src = fetchurl {
1234 src = fetchurl {
1245 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
1235 url = "https://pypi.python.org/packages/c1/d3/3919492d5e57d8dd01b36f30b34fc8404a30577392b1eb817c303499ad20/zope.deprecation-4.1.2.tar.gz";
1246 md5 = "e9a663ded58f4f9f7881beb56cae2782";
1236 md5 = "e9a663ded58f4f9f7881beb56cae2782";
1247 };
1237 };
1248 };
1238 };
1249 zope.event = super.buildPythonPackage {
1239 zope.event = super.buildPythonPackage {
1250 name = "zope.event-4.0.3";
1240 name = "zope.event-4.0.3";
1251 buildInputs = with self; [];
1241 buildInputs = with self; [];
1252 doCheck = false;
1242 doCheck = false;
1253 propagatedBuildInputs = with self; [setuptools];
1243 propagatedBuildInputs = with self; [setuptools];
1254 src = fetchurl {
1244 src = fetchurl {
1255 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
1245 url = "https://pypi.python.org/packages/c1/29/91ba884d7d6d96691df592e9e9c2bfa57a47040ec1ff47eff18c85137152/zope.event-4.0.3.tar.gz";
1256 md5 = "9a3780916332b18b8b85f522bcc3e249";
1246 md5 = "9a3780916332b18b8b85f522bcc3e249";
1257 };
1247 };
1258 };
1248 };
1259 zope.interface = super.buildPythonPackage {
1249 zope.interface = super.buildPythonPackage {
1260 name = "zope.interface-4.1.3";
1250 name = "zope.interface-4.1.3";
1261 buildInputs = with self; [];
1251 buildInputs = with self; [];
1262 doCheck = false;
1252 doCheck = false;
1263 propagatedBuildInputs = with self; [setuptools];
1253 propagatedBuildInputs = with self; [setuptools];
1264 src = fetchurl {
1254 src = fetchurl {
1265 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
1255 url = "https://pypi.python.org/packages/9d/81/2509ca3c6f59080123c1a8a97125eb48414022618cec0e64eb1313727bfe/zope.interface-4.1.3.tar.gz";
1266 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
1256 md5 = "9ae3d24c0c7415deb249dd1a132f0f79";
1267 };
1257 };
1268 };
1258 };
1269
1259
1270 ### Test requirements
1260 ### Test requirements
1271
1261
1272
1262
1273 }
1263 }
@@ -1,151 +1,151 b''
1 Babel==1.3
1 Babel==1.3
2 Beaker==1.7.0
2 Beaker==1.7.0
3 CProfileV==1.0.6
3 CProfileV==1.0.6
4 Fabric==1.10.0
4 Fabric==1.10.0
5 FormEncode==1.2.4
5 FormEncode==1.2.4
6 Jinja2==2.7.3
6 Jinja2==2.7.3
7 Mako==1.0.1
7 Mako==1.0.1
8 Markdown==2.6.2
8 Markdown==2.6.2
9 MarkupSafe==0.23
9 MarkupSafe==0.23
10 MySQL-python==1.2.5
10 MySQL-python==1.2.5
11 Paste==2.0.2
11 Paste==2.0.2
12 PasteDeploy==1.5.2
12 PasteDeploy==1.5.2
13 PasteScript==1.7.5
13 PasteScript==1.7.5
14 pyelasticsearch==1.4
15 Pygments==2.0.2
14 Pygments==2.0.2
16
15
17 # TODO: This version is not available on PyPI
16 # TODO: This version is not available on PyPI
18 # Pylons==1.0.2.dev20160108
17 # Pylons==1.0.2.dev20160108
19 Pylons==1.0.1
18 Pylons==1.0.1
20
19
21 # TODO: This version is not available, but newer ones are
20 # TODO: This version is not available, but newer ones are
22 # Pyro4==4.35
21 # Pyro4==4.35
23 Pyro4==4.41
22 Pyro4==4.41
24
23
25 # TODO: This should probably not be in here
24 # TODO: This should probably not be in here
26 # -e hg+https://johbo@code.rhodecode.com/johbo/rhodecode-fork@3a454bd1f17c0b2b2a951cf2b111e0320d7942a9#egg=RhodeCodeEnterprise-dev
25 # -e hg+https://johbo@code.rhodecode.com/johbo/rhodecode-fork@3a454bd1f17c0b2b2a951cf2b111e0320d7942a9#egg=RhodeCodeEnterprise-dev
27
26
28 # TODO: This is not really a dependency, we should add it only
27 # TODO: This is not really a dependency, we should add it only
29 # into the development environment, since there it is useful.
28 # into the development environment, since there it is useful.
30 # RhodeCodeVCSServer==3.9.0
29 # RhodeCodeVCSServer==3.9.0
31
30
32 Routes==1.13
31 Routes==1.13
33 SQLAlchemy==0.9.9
32 SQLAlchemy==0.9.9
34 Sphinx==1.2.2
33 Sphinx==1.2.2
35 Tempita==0.5.2
34 Tempita==0.5.2
36 URLObject==2.4.0
35 URLObject==2.4.0
37 WebError==0.10.3
36 WebError==0.10.3
38
37
39 # TODO: This is modified by us, needs a better integration. For now
38 # TODO: This is modified by us, needs a better integration. For now
40 # using the latest version before.
39 # using the latest version before.
41 # WebHelpers==1.3.dev20150807
40 # WebHelpers==1.3.dev20150807
42 WebHelpers==1.3
41 WebHelpers==1.3
43
42
44 WebHelpers2==2.0
43 WebHelpers2==2.0
45 WebOb==1.3.1
44 WebOb==1.3.1
46 WebTest==1.4.3
45 WebTest==1.4.3
47 Whoosh==2.7.0
46 Whoosh==2.7.0
48 alembic==0.8.4
47 alembic==0.8.4
49 amqplib==1.0.2
48 amqplib==1.0.2
50 anyjson==0.3.3
49 anyjson==0.3.3
51 appenlight-client==0.6.14
50 appenlight-client==0.6.14
52 authomatic==0.1.0.post1;
51 authomatic==0.1.0.post1;
53 backport-ipaddress==0.1
52 backport-ipaddress==0.1
54 bottle==0.12.8
53 bottle==0.12.8
55 bumpversion==0.5.3
54 bumpversion==0.5.3
56 celery==2.2.10
55 celery==2.2.10
57 click==5.1
56 click==5.1
58 colander==1.2
57 colander==1.2
59 configobj==5.0.6
58 configobj==5.0.6
60 cov-core==1.15.0
59 cov-core==1.15.0
61 coverage==3.7.1
60 coverage==3.7.1
62 cssselect==0.9.1
61 cssselect==0.9.1
63 decorator==3.4.2
62 decorator==3.4.2
64 docutils==0.12
63 docutils==0.12
65 dogpile.cache==0.5.7
64 dogpile.cache==0.5.7
66 dogpile.core==0.4.1
65 dogpile.core==0.4.1
67 dulwich==0.12.0
66 dulwich==0.12.0
68 ecdsa==0.11
67 ecdsa==0.11
69 flake8==2.4.1
68 flake8==2.4.1
70 future==0.14.3
69 future==0.14.3
71 futures==3.0.2
70 futures==3.0.2
72 gprof2dot==2015.12.1
71 gprof2dot==2015.12.1
73 greenlet==0.4.7
72 greenlet==0.4.9
74 gunicorn==19.6.0
73 gunicorn==19.6.0
75
74
76 # TODO: Needs subvertpy and blows up without Subversion headers,
75 # TODO: Needs subvertpy and blows up without Subversion headers,
77 # actually we should not need this for Enterprise at all.
76 # actually we should not need this for Enterprise at all.
78 # hgsubversion==1.8.2
77 # hgsubversion==1.8.2
79
78
79 gnureadline==6.3.3
80 infrae.cache==1.0.1
80 infrae.cache==1.0.1
81 invoke==0.11.1
81 invoke==0.11.1
82 ipdb==0.8
82 ipdb==0.8
83 ipython==3.1.0
83 ipython==3.1.0
84 iso8601==0.1.11
84 iso8601==0.1.11
85 itsdangerous==0.24
85 itsdangerous==0.24
86 kombu==1.5.1
86 kombu==1.5.1
87 lxml==3.4.4
87 lxml==3.4.4
88 mccabe==0.3
88 mccabe==0.3
89 meld3==1.0.2
89 meld3==1.0.2
90 mock==1.0.1
90 mock==1.0.1
91 msgpack-python==0.4.6
91 msgpack-python==0.4.6
92 nose==1.3.6
92 nose==1.3.6
93 objgraph==2.0.0
93 objgraph==2.0.0
94 packaging==15.2
94 packaging==15.2
95 paramiko==1.15.1
95 paramiko==1.15.1
96 pep8==1.5.7
96 pep8==1.5.7
97 psutil==2.2.1
97 psutil==2.2.1
98 psycopg2==2.6
98 psycopg2==2.6
99 py==1.4.29
99 py==1.4.29
100 py-bcrypt==0.4
100 py-bcrypt==0.4
101 pycrypto==2.6.1
101 pycrypto==2.6.1
102 pycurl==7.19.5
102 pycurl==7.19.5
103 pyflakes==0.8.1
103 pyflakes==0.8.1
104 pyparsing==1.5.7
104 pyparsing==1.5.7
105 pyramid==1.6.1
105 pyramid==1.6.1
106 pyramid-beaker==0.8
106 pyramid-beaker==0.8
107 pyramid-debugtoolbar==2.4.2
107 pyramid-debugtoolbar==2.4.2
108 pyramid-jinja2==2.5
108 pyramid-jinja2==2.5
109 pyramid-mako==1.0.2
109 pyramid-mako==1.0.2
110 pysqlite==2.6.3
110 pysqlite==2.6.3
111 pytest==2.8.5
111 pytest==2.8.5
112 pytest-runner==2.7.1
112 pytest-runner==2.7.1
113 pytest-catchlog==1.2.2
113 pytest-catchlog==1.2.2
114 pytest-cov==1.8.1
114 pytest-cov==1.8.1
115 pytest-profiling==1.0.1
115 pytest-profiling==1.0.1
116 pytest-timeout==0.4
116 pytest-timeout==0.4
117 python-dateutil==1.5
117 python-dateutil==1.5
118 python-ldap==2.4.19
118 python-ldap==2.4.19
119 python-memcached==1.57
119 python-memcached==1.57
120 python-pam==1.8.2
120 python-pam==1.8.2
121 pytz==2015.4
121 pytz==2015.4
122 pyzmq==14.6.0
122 pyzmq==14.6.0
123
123
124 # TODO: This is not available in public
124 # TODO: This is not available in public
125 # rc-testdata==0.2.0
125 # rc-testdata==0.2.0
126
126
127 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.7.1.zip#md5=91daea803aaa264ce7a8213bc2220d4c
127 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.8.3.zip#md5=9acdfd71b8ddf4056057065f37ab9ccb
128
128
129
129
130 recaptcha-client==1.0.6
130 recaptcha-client==1.0.6
131 repoze.lru==0.6
131 repoze.lru==0.6
132 requests==2.9.1
132 requests==2.9.1
133 serpent==1.12
133 serpent==1.12
134 setproctitle==1.1.8
134 setproctitle==1.1.8
135 setuptools==20.8.1
135 setuptools==20.8.1
136 setuptools-scm==1.11.0
136 setuptools-scm==1.11.0
137 simplejson==3.7.2
137 simplejson==3.7.2
138 six==1.9.0
138 six==1.9.0
139 subprocess32==3.2.6
139 subprocess32==3.2.6
140 supervisor==3.1.3
140 supervisor==3.1.3
141 transifex-client==0.10
141 transifex-client==0.10
142 translationstring==1.3
142 translationstring==1.3
143 trollius==1.0.4
143 trollius==1.0.4
144 uWSGI==2.0.11.2
144 uWSGI==2.0.11.2
145 venusian==1.0
145 venusian==1.0
146 waitress==0.8.9
146 waitress==0.8.9
147 wsgiref==0.1.2
147 wsgiref==0.1.2
148 zope.cachedescriptors==4.0.0
148 zope.cachedescriptors==4.0.0
149 zope.deprecation==4.1.2
149 zope.deprecation==4.1.2
150 zope.event==4.0.3
150 zope.event==4.0.3
151 zope.interface==4.1.3
151 zope.interface==4.1.3
@@ -1,1 +1,1 b''
1 4.0.1 No newline at end of file
1 4.1.0 No newline at end of file
@@ -1,58 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Linked module for extensions
46 # Linked module for extensions
47 EXTENSIONS = {}
47 EXTENSIONS = {}
48
48
49 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
49 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
50 __dbversion__ = 51 # defines current db version for migrations
50 __dbversion__ = 54 # defines current db version for migrations
51 __platform__ = platform.system()
51 __platform__ = platform.system()
52 __license__ = 'AGPLv3, and Commercial License'
52 __license__ = 'AGPLv3, and Commercial License'
53 __author__ = 'RhodeCode GmbH'
53 __author__ = 'RhodeCode GmbH'
54 __url__ = 'http://rhodecode.com'
54 __url__ = 'http://rhodecode.com'
55
55
56 is_windows = __platform__ in ['Windows']
56 is_windows = __platform__ in ['Windows']
57 is_unix = not is_windows
57 is_unix = not is_windows
58 is_test = False
58 is_test = False
@@ -1,144 +1,158 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33 UPDATE_REPO_NAME = 'api_update_me'
34
35 class SAME_AS_UPDATES(object): """ Constant used for tests below """
33
36
34 @pytest.mark.usefixtures("testuser_api", "app")
37 @pytest.mark.usefixtures("testuser_api", "app")
35 class TestApiUpdateRepo(object):
38 class TestApiUpdateRepo(object):
36 @pytest.mark.parametrize("changing_attr, updates", [
39
37 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
40 @pytest.mark.parametrize("updates, expected", [
38 ('description', {'description': 'new description'}),
41 ({'owner': TEST_USER_REGULAR_LOGIN}, SAME_AS_UPDATES),
39 ('active', {'active': True}),
42 ({'description': 'new description'}, SAME_AS_UPDATES),
40 ('active', {'active': False}),
43 ({'clone_uri': 'http://foo.com/repo'}, SAME_AS_UPDATES),
41 ('clone_uri', {'clone_uri': 'http://foo.com/repo'}),
44 ({'clone_uri': None}, {'clone_uri': ''}),
42 ('clone_uri', {'clone_uri': None}),
45 ({'clone_uri': ''}, {'clone_uri': ''}),
43 ('landing_rev', {'landing_rev': 'branch:master'}),
46 ({'landing_rev': 'branch:master'}, {'landing_rev': ['branch','master']}),
44 ('enable_statistics', {'enable_statistics': True}),
47 ({'enable_statistics': True}, SAME_AS_UPDATES),
45 ('enable_locking', {'enable_locking': True}),
48 ({'enable_locking': True}, SAME_AS_UPDATES),
46 ('enable_downloads', {'enable_downloads': True}),
49 ({'enable_downloads': True}, SAME_AS_UPDATES),
47 ('name', {'name': 'new_repo_name'}),
50 ({'name': 'new_repo_name'}, {'repo_name': 'new_repo_name'}),
48 ('repo_group', {'group': 'test_group_for_update'}),
51 ({'group': 'test_group_for_update'},
52 {'repo_name': 'test_group_for_update/%s' % UPDATE_REPO_NAME}),
49 ])
53 ])
50 def test_api_update_repo(self, changing_attr, updates, backend):
54 def test_api_update_repo(self, updates, expected, backend):
51 repo_name = 'api_update_me'
55 repo_name = UPDATE_REPO_NAME
52 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
56 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
53 if changing_attr == 'repo_group':
57 if updates.get('group'):
54 fixture.create_repo_group(updates['group'])
58 fixture.create_repo_group(updates['group'])
55
59
60 expected_api_data = repo.get_api_data(include_secrets=True)
61 if expected is SAME_AS_UPDATES:
62 expected_api_data.update(updates)
63 else:
64 expected_api_data.update(expected)
65
66
56 id_, params = build_data(
67 id_, params = build_data(
57 self.apikey, 'update_repo', repoid=repo_name, **updates)
68 self.apikey, 'update_repo', repoid=repo_name, **updates)
58 response = api_call(self.app, params)
69 response = api_call(self.app, params)
59 if changing_attr == 'name':
70
71 if updates.get('name'):
60 repo_name = updates['name']
72 repo_name = updates['name']
61 if changing_attr == 'repo_group':
73 if updates.get('group'):
62 repo_name = '/'.join([updates['group'], repo_name])
74 repo_name = '/'.join([updates['group'], repo_name])
75
63 try:
76 try:
64 expected = {
77 expected = {
65 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
78 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
66 'repository': repo.get_api_data(include_secrets=True)
79 'repository': jsonify(expected_api_data)
67 }
80 }
68 assert_ok(id_, expected, given=response.body)
81 assert_ok(id_, expected, given=response.body)
69 finally:
82 finally:
70 fixture.destroy_repo(repo_name)
83 fixture.destroy_repo(repo_name)
71 if changing_attr == 'repo_group':
84 if updates.get('group'):
72
73 fixture.destroy_repo_group(updates['group'])
85 fixture.destroy_repo_group(updates['group'])
74
86
75 def test_api_update_repo_fork_of_field(self, backend):
87 def test_api_update_repo_fork_of_field(self, backend):
76 master_repo = backend.create_repo()
88 master_repo = backend.create_repo()
77 repo = backend.create_repo()
89 repo = backend.create_repo()
78
79 updates = {
90 updates = {
80 'fork_of': master_repo.repo_name
91 'fork_of': master_repo.repo_name
81 }
92 }
93 expected_api_data = repo.get_api_data(include_secrets=True)
94 expected_api_data.update(updates)
95
82 id_, params = build_data(
96 id_, params = build_data(
83 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
97 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
84 response = api_call(self.app, params)
98 response = api_call(self.app, params)
85 expected = {
99 expected = {
86 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
100 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
87 'repository': repo.get_api_data(include_secrets=True)
101 'repository': jsonify(expected_api_data)
88 }
102 }
89 assert_ok(id_, expected, given=response.body)
103 assert_ok(id_, expected, given=response.body)
90 result = response.json['result']['repository']
104 result = response.json['result']['repository']
91 assert result['fork_of'] == master_repo.repo_name
105 assert result['fork_of'] == master_repo.repo_name
92
106
93 def test_api_update_repo_fork_of_not_found(self, backend):
107 def test_api_update_repo_fork_of_not_found(self, backend):
94 master_repo_name = 'fake-parent-repo'
108 master_repo_name = 'fake-parent-repo'
95 repo = backend.create_repo()
109 repo = backend.create_repo()
96 updates = {
110 updates = {
97 'fork_of': master_repo_name
111 'fork_of': master_repo_name
98 }
112 }
99 id_, params = build_data(
113 id_, params = build_data(
100 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
114 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
101 response = api_call(self.app, params)
115 response = api_call(self.app, params)
102 expected = 'repository `{}` does not exist'.format(master_repo_name)
116 expected = 'repository `{}` does not exist'.format(master_repo_name)
103 assert_error(id_, expected, given=response.body)
117 assert_error(id_, expected, given=response.body)
104
118
105 def test_api_update_repo_with_repo_group_not_existing(self):
119 def test_api_update_repo_with_repo_group_not_existing(self):
106 repo_name = 'admin_owned'
120 repo_name = 'admin_owned'
107 fixture.create_repo(repo_name)
121 fixture.create_repo(repo_name)
108 updates = {'group': 'test_group_for_update'}
122 updates = {'group': 'test_group_for_update'}
109 id_, params = build_data(
123 id_, params = build_data(
110 self.apikey, 'update_repo', repoid=repo_name, **updates)
124 self.apikey, 'update_repo', repoid=repo_name, **updates)
111 response = api_call(self.app, params)
125 response = api_call(self.app, params)
112 try:
126 try:
113 expected = 'repository group `%s` does not exist' % (
127 expected = 'repository group `%s` does not exist' % (
114 updates['group'],)
128 updates['group'],)
115 assert_error(id_, expected, given=response.body)
129 assert_error(id_, expected, given=response.body)
116 finally:
130 finally:
117 fixture.destroy_repo(repo_name)
131 fixture.destroy_repo(repo_name)
118
132
119 def test_api_update_repo_regular_user_not_allowed(self):
133 def test_api_update_repo_regular_user_not_allowed(self):
120 repo_name = 'admin_owned'
134 repo_name = 'admin_owned'
121 fixture.create_repo(repo_name)
135 fixture.create_repo(repo_name)
122 updates = {'active': False}
136 updates = {'active': False}
123 id_, params = build_data(
137 id_, params = build_data(
124 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
138 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
125 response = api_call(self.app, params)
139 response = api_call(self.app, params)
126 try:
140 try:
127 expected = 'repository `%s` does not exist' % (repo_name,)
141 expected = 'repository `%s` does not exist' % (repo_name,)
128 assert_error(id_, expected, given=response.body)
142 assert_error(id_, expected, given=response.body)
129 finally:
143 finally:
130 fixture.destroy_repo(repo_name)
144 fixture.destroy_repo(repo_name)
131
145
132 @mock.patch.object(RepoModel, 'update', crash)
146 @mock.patch.object(RepoModel, 'update', crash)
133 def test_api_update_repo_exception_occurred(self, backend):
147 def test_api_update_repo_exception_occurred(self, backend):
134 repo_name = 'api_update_me'
148 repo_name = UPDATE_REPO_NAME
135 fixture.create_repo(repo_name, repo_type=backend.alias)
149 fixture.create_repo(repo_name, repo_type=backend.alias)
136 id_, params = build_data(
150 id_, params = build_data(
137 self.apikey, 'update_repo', repoid=repo_name,
151 self.apikey, 'update_repo', repoid=repo_name,
138 owner=TEST_USER_ADMIN_LOGIN,)
152 owner=TEST_USER_ADMIN_LOGIN,)
139 response = api_call(self.app, params)
153 response = api_call(self.app, params)
140 try:
154 try:
141 expected = 'failed to update repo `%s`' % (repo_name,)
155 expected = 'failed to update repo `%s`' % (repo_name,)
142 assert_error(id_, expected, given=response.body)
156 assert_error(id_, expected, given=response.body)
143 finally:
157 finally:
144 fixture.destroy_repo(repo_name)
158 fixture.destroy_repo(repo_name)
@@ -1,100 +1,108 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
26 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdateUserGroup(object):
32 class TestUpdateUserGroup(object):
33 @pytest.mark.parametrize("changing_attr, updates", [
33 @pytest.mark.parametrize("changing_attr, updates", [
34 ('group_name', {'group_name': 'new_group_name'}),
34 ('group_name', {'group_name': 'new_group_name'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
36 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 ('active', {'active': False}),
37 ('active', {'active': False}),
38 ('active', {'active': True})
38 ('active', {'active': True})
39 ])
39 ])
40 def test_api_update_user_group(self, changing_attr, updates, user_util):
40 def test_api_update_user_group(self, changing_attr, updates, user_util):
41 user_group = user_util.create_user_group()
41 user_group = user_util.create_user_group()
42 group_name = user_group.users_group_name
42 group_name = user_group.users_group_name
43 expected_api_data = user_group.get_api_data()
44 expected_api_data.update(updates)
45
43 id_, params = build_data(
46 id_, params = build_data(
44 self.apikey, 'update_user_group', usergroupid=group_name,
47 self.apikey, 'update_user_group', usergroupid=group_name,
45 **updates)
48 **updates)
46 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
47 expected = {
51 expected = {
48 'msg': 'updated user group ID:%s %s' % (
52 'msg': 'updated user group ID:%s %s' % (
49 user_group.users_group_id, user_group.users_group_name),
53 user_group.users_group_id, user_group.users_group_name),
50 'user_group': user_group.get_api_data()
54 'user_group': jsonify(expected_api_data)
51 }
55 }
52 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
53
57
54 @pytest.mark.parametrize("changing_attr, updates", [
58 @pytest.mark.parametrize("changing_attr, updates", [
55 # TODO: mikhail: decide if we need to test against the commented params
59 # TODO: mikhail: decide if we need to test against the commented params
56 # ('group_name', {'group_name': 'new_group_name'}),
60 # ('group_name', {'group_name': 'new_group_name'}),
57 # ('group_name', {'group_name': 'test_group_for_update'}),
61 # ('group_name', {'group_name': 'test_group_for_update'}),
58 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
62 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
59 ('active', {'active': False}),
63 ('active', {'active': False}),
60 ('active', {'active': True})
64 ('active', {'active': True})
61 ])
65 ])
62 def test_api_update_user_group_regular_user(
66 def test_api_update_user_group_regular_user(
63 self, changing_attr, updates, user_util):
67 self, changing_attr, updates, user_util):
64 user_group = user_util.create_user_group()
68 user_group = user_util.create_user_group()
65 group_name = user_group.users_group_name
69 group_name = user_group.users_group_name
70 expected_api_data = user_group.get_api_data()
71 expected_api_data.update(updates)
72
73
66 # grant permission to this user
74 # grant permission to this user
67 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
75 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
68
76
69 user_util.grant_user_permission_to_user_group(
77 user_util.grant_user_permission_to_user_group(
70 user_group, user, 'usergroup.admin')
78 user_group, user, 'usergroup.admin')
71 id_, params = build_data(
79 id_, params = build_data(
72 self.apikey_regular, 'update_user_group',
80 self.apikey_regular, 'update_user_group',
73 usergroupid=group_name, **updates)
81 usergroupid=group_name, **updates)
74 response = api_call(self.app, params)
82 response = api_call(self.app, params)
75 expected = {
83 expected = {
76 'msg': 'updated user group ID:%s %s' % (
84 'msg': 'updated user group ID:%s %s' % (
77 user_group.users_group_id, user_group.users_group_name),
85 user_group.users_group_id, user_group.users_group_name),
78 'user_group': user_group.get_api_data()
86 'user_group': jsonify(expected_api_data)
79 }
87 }
80 assert_ok(id_, expected, given=response.body)
88 assert_ok(id_, expected, given=response.body)
81
89
82 def test_api_update_user_group_regular_user_no_permission(self, user_util):
90 def test_api_update_user_group_regular_user_no_permission(self, user_util):
83 user_group = user_util.create_user_group()
91 user_group = user_util.create_user_group()
84 group_name = user_group.users_group_name
92 group_name = user_group.users_group_name
85 id_, params = build_data(
93 id_, params = build_data(
86 self.apikey_regular, 'update_user_group', usergroupid=group_name)
94 self.apikey_regular, 'update_user_group', usergroupid=group_name)
87 response = api_call(self.app, params)
95 response = api_call(self.app, params)
88
96
89 expected = 'user group `%s` does not exist' % (group_name)
97 expected = 'user group `%s` does not exist' % (group_name)
90 assert_error(id_, expected, given=response.body)
98 assert_error(id_, expected, given=response.body)
91
99
92 @mock.patch.object(UserGroupModel, 'update', crash)
100 @mock.patch.object(UserGroupModel, 'update', crash)
93 def test_api_update_user_group_exception_occurred(self, user_util):
101 def test_api_update_user_group_exception_occurred(self, user_util):
94 user_group = user_util.create_user_group()
102 user_group = user_util.create_user_group()
95 group_name = user_group.users_group_name
103 group_name = user_group.users_group_name
96 id_, params = build_data(
104 id_, params = build_data(
97 self.apikey, 'update_user_group', usergroupid=group_name)
105 self.apikey, 'update_user_group', usergroupid=group_name)
98 response = api_call(self.app, params)
106 response = api_call(self.app, params)
99 expected = 'failed to update user group `%s`' % (group_name,)
107 expected = 'failed to update user group `%s`' % (group_name,)
100 assert_error(id_, expected, given=response.body)
108 assert_error(id_, expected, given=response.body)
@@ -1,1774 +1,1777 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import colander
24 import colander
25
25
26 from rhodecode import BACKENDS
26 from rhodecode import BACKENDS
27 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden, json
27 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden, json
28 from rhodecode.api.utils import (
28 from rhodecode.api.utils import (
29 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
30 get_user_group_or_error, get_user_or_error, has_repo_permissions,
30 get_user_group_or_error, get_user_or_error, has_repo_permissions,
31 get_perm_or_error, store_update, get_repo_group_or_error, parse_args,
31 get_perm_or_error, store_update, get_repo_group_or_error, parse_args,
32 get_origin, build_commit_data)
32 get_origin, build_commit_data)
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 HasPermissionAnyApi, HasRepoGroupPermissionAnyApi,
34 HasPermissionAnyApi, HasRepoGroupPermissionAnyApi,
35 HasUserGroupPermissionAnyApi)
35 HasUserGroupPermissionAnyApi)
36 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
36 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
37 from rhodecode.lib.utils import map_groups
37 from rhodecode.lib.utils import map_groups
38 from rhodecode.lib.utils2 import str2bool, time_to_datetime
38 from rhodecode.lib.utils2 import str2bool, time_to_datetime
39 from rhodecode.model.changeset_status import ChangesetStatusModel
39 from rhodecode.model.changeset_status import ChangesetStatusModel
40 from rhodecode.model.comment import ChangesetCommentsModel
40 from rhodecode.model.comment import ChangesetCommentsModel
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 Session, ChangesetStatus, RepositoryField, Repository)
42 Session, ChangesetStatus, RepositoryField, Repository)
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo_group import RepoGroupModel
44 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.scm import ScmModel, RepoList
45 from rhodecode.model.scm import ScmModel, RepoList
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.validation_schema import RepoSchema
47 from rhodecode.model.validation_schema import RepoSchema
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 @jsonrpc_method()
52 @jsonrpc_method()
53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
54 """
54 """
55 Gets an existing repository by its name or repository_id.
55 Gets an existing repository by its name or repository_id.
56
56
57 The members section so the output returns users groups or users
57 The members section so the output returns users groups or users
58 associated with that repository.
58 associated with that repository.
59
59
60 This command can only be run using an |authtoken| with admin rights,
60 This command can only be run using an |authtoken| with admin rights,
61 or users with at least read rights to the |repo|.
61 or users with at least read rights to the |repo|.
62
62
63 :param apiuser: This is filled automatically from the |authtoken|.
63 :param apiuser: This is filled automatically from the |authtoken|.
64 :type apiuser: AuthUser
64 :type apiuser: AuthUser
65 :param repoid: The repository name or repository id.
65 :param repoid: The repository name or repository id.
66 :type repoid: str or int
66 :type repoid: str or int
67 :param cache: use the cached value for last changeset
67 :param cache: use the cached value for last changeset
68 :type: cache: Optional(bool)
68 :type: cache: Optional(bool)
69
69
70 Example output:
70 Example output:
71
71
72 .. code-block:: bash
72 .. code-block:: bash
73
73
74 {
74 {
75 "error": null,
75 "error": null,
76 "id": <repo_id>,
76 "id": <repo_id>,
77 "result": {
77 "result": {
78 "clone_uri": null,
78 "clone_uri": null,
79 "created_on": "timestamp",
79 "created_on": "timestamp",
80 "description": "repo description",
80 "description": "repo description",
81 "enable_downloads": false,
81 "enable_downloads": false,
82 "enable_locking": false,
82 "enable_locking": false,
83 "enable_statistics": false,
83 "enable_statistics": false,
84 "followers": [
84 "followers": [
85 {
85 {
86 "active": true,
86 "active": true,
87 "admin": false,
87 "admin": false,
88 "api_key": "****************************************",
88 "api_key": "****************************************",
89 "api_keys": [
89 "api_keys": [
90 "****************************************"
90 "****************************************"
91 ],
91 ],
92 "email": "user@example.com",
92 "email": "user@example.com",
93 "emails": [
93 "emails": [
94 "user@example.com"
94 "user@example.com"
95 ],
95 ],
96 "extern_name": "rhodecode",
96 "extern_name": "rhodecode",
97 "extern_type": "rhodecode",
97 "extern_type": "rhodecode",
98 "firstname": "username",
98 "firstname": "username",
99 "ip_addresses": [],
99 "ip_addresses": [],
100 "language": null,
100 "language": null,
101 "last_login": "2015-09-16T17:16:35.854",
101 "last_login": "2015-09-16T17:16:35.854",
102 "lastname": "surname",
102 "lastname": "surname",
103 "user_id": <user_id>,
103 "user_id": <user_id>,
104 "username": "name"
104 "username": "name"
105 }
105 }
106 ],
106 ],
107 "fork_of": "parent-repo",
107 "fork_of": "parent-repo",
108 "landing_rev": [
108 "landing_rev": [
109 "rev",
109 "rev",
110 "tip"
110 "tip"
111 ],
111 ],
112 "last_changeset": {
112 "last_changeset": {
113 "author": "User <user@example.com>",
113 "author": "User <user@example.com>",
114 "branch": "default",
114 "branch": "default",
115 "date": "timestamp",
115 "date": "timestamp",
116 "message": "last commit message",
116 "message": "last commit message",
117 "parents": [
117 "parents": [
118 {
118 {
119 "raw_id": "commit-id"
119 "raw_id": "commit-id"
120 }
120 }
121 ],
121 ],
122 "raw_id": "commit-id",
122 "raw_id": "commit-id",
123 "revision": <revision number>,
123 "revision": <revision number>,
124 "short_id": "short id"
124 "short_id": "short id"
125 },
125 },
126 "lock_reason": null,
126 "lock_reason": null,
127 "locked_by": null,
127 "locked_by": null,
128 "locked_date": null,
128 "locked_date": null,
129 "members": [
129 "members": [
130 {
130 {
131 "name": "super-admin-name",
131 "name": "super-admin-name",
132 "origin": "super-admin",
132 "origin": "super-admin",
133 "permission": "repository.admin",
133 "permission": "repository.admin",
134 "type": "user"
134 "type": "user"
135 },
135 },
136 {
136 {
137 "name": "owner-name",
137 "name": "owner-name",
138 "origin": "owner",
138 "origin": "owner",
139 "permission": "repository.admin",
139 "permission": "repository.admin",
140 "type": "user"
140 "type": "user"
141 },
141 },
142 {
142 {
143 "name": "user-group-name",
143 "name": "user-group-name",
144 "origin": "permission",
144 "origin": "permission",
145 "permission": "repository.write",
145 "permission": "repository.write",
146 "type": "user_group"
146 "type": "user_group"
147 }
147 }
148 ],
148 ],
149 "owner": "owner-name",
149 "owner": "owner-name",
150 "permissions": [
150 "permissions": [
151 {
151 {
152 "name": "super-admin-name",
152 "name": "super-admin-name",
153 "origin": "super-admin",
153 "origin": "super-admin",
154 "permission": "repository.admin",
154 "permission": "repository.admin",
155 "type": "user"
155 "type": "user"
156 },
156 },
157 {
157 {
158 "name": "owner-name",
158 "name": "owner-name",
159 "origin": "owner",
159 "origin": "owner",
160 "permission": "repository.admin",
160 "permission": "repository.admin",
161 "type": "user"
161 "type": "user"
162 },
162 },
163 {
163 {
164 "name": "user-group-name",
164 "name": "user-group-name",
165 "origin": "permission",
165 "origin": "permission",
166 "permission": "repository.write",
166 "permission": "repository.write",
167 "type": "user_group"
167 "type": "user_group"
168 }
168 }
169 ],
169 ],
170 "private": true,
170 "private": true,
171 "repo_id": 676,
171 "repo_id": 676,
172 "repo_name": "user-group/repo-name",
172 "repo_name": "user-group/repo-name",
173 "repo_type": "hg"
173 "repo_type": "hg"
174 }
174 }
175 }
175 }
176 """
176 """
177
177
178 repo = get_repo_or_error(repoid)
178 repo = get_repo_or_error(repoid)
179 cache = Optional.extract(cache)
179 cache = Optional.extract(cache)
180 include_secrets = False
180 include_secrets = False
181 if has_superadmin_permission(apiuser):
181 if has_superadmin_permission(apiuser):
182 include_secrets = True
182 include_secrets = True
183 else:
183 else:
184 # check if we have at least read permission for this repo !
184 # check if we have at least read permission for this repo !
185 _perms = (
185 _perms = (
186 'repository.admin', 'repository.write', 'repository.read',)
186 'repository.admin', 'repository.write', 'repository.read',)
187 has_repo_permissions(apiuser, repoid, repo, _perms)
187 has_repo_permissions(apiuser, repoid, repo, _perms)
188
188
189 permissions = []
189 permissions = []
190 for _user in repo.permissions():
190 for _user in repo.permissions():
191 user_data = {
191 user_data = {
192 'name': _user.username,
192 'name': _user.username,
193 'permission': _user.permission,
193 'permission': _user.permission,
194 'origin': get_origin(_user),
194 'origin': get_origin(_user),
195 'type': "user",
195 'type': "user",
196 }
196 }
197 permissions.append(user_data)
197 permissions.append(user_data)
198
198
199 for _user_group in repo.permission_user_groups():
199 for _user_group in repo.permission_user_groups():
200 user_group_data = {
200 user_group_data = {
201 'name': _user_group.users_group_name,
201 'name': _user_group.users_group_name,
202 'permission': _user_group.permission,
202 'permission': _user_group.permission,
203 'origin': get_origin(_user_group),
203 'origin': get_origin(_user_group),
204 'type': "user_group",
204 'type': "user_group",
205 }
205 }
206 permissions.append(user_group_data)
206 permissions.append(user_group_data)
207
207
208 following_users = [
208 following_users = [
209 user.user.get_api_data(include_secrets=include_secrets)
209 user.user.get_api_data(include_secrets=include_secrets)
210 for user in repo.followers]
210 for user in repo.followers]
211
211
212 if not cache:
212 if not cache:
213 repo.update_commit_cache()
213 repo.update_commit_cache()
214 data = repo.get_api_data(include_secrets=include_secrets)
214 data = repo.get_api_data(include_secrets=include_secrets)
215 data['members'] = permissions # TODO: this should be deprecated soon
215 data['members'] = permissions # TODO: this should be deprecated soon
216 data['permissions'] = permissions
216 data['permissions'] = permissions
217 data['followers'] = following_users
217 data['followers'] = following_users
218 return data
218 return data
219
219
220
220
221 @jsonrpc_method()
221 @jsonrpc_method()
222 def get_repos(request, apiuser):
222 def get_repos(request, apiuser):
223 """
223 """
224 Lists all existing repositories.
224 Lists all existing repositories.
225
225
226 This command can only be run using an |authtoken| with admin rights,
226 This command can only be run using an |authtoken| with admin rights,
227 or users with at least read rights to |repos|.
227 or users with at least read rights to |repos|.
228
228
229 :param apiuser: This is filled automatically from the |authtoken|.
229 :param apiuser: This is filled automatically from the |authtoken|.
230 :type apiuser: AuthUser
230 :type apiuser: AuthUser
231
231
232 Example output:
232 Example output:
233
233
234 .. code-block:: bash
234 .. code-block:: bash
235
235
236 id : <id_given_in_input>
236 id : <id_given_in_input>
237 result: [
237 result: [
238 {
238 {
239 "repo_id" : "<repo_id>",
239 "repo_id" : "<repo_id>",
240 "repo_name" : "<reponame>"
240 "repo_name" : "<reponame>"
241 "repo_type" : "<repo_type>",
241 "repo_type" : "<repo_type>",
242 "clone_uri" : "<clone_uri>",
242 "clone_uri" : "<clone_uri>",
243 "private": : "<bool>",
243 "private": : "<bool>",
244 "created_on" : "<datetimecreated>",
244 "created_on" : "<datetimecreated>",
245 "description" : "<description>",
245 "description" : "<description>",
246 "landing_rev": "<landing_rev>",
246 "landing_rev": "<landing_rev>",
247 "owner": "<repo_owner>",
247 "owner": "<repo_owner>",
248 "fork_of": "<name_of_fork_parent>",
248 "fork_of": "<name_of_fork_parent>",
249 "enable_downloads": "<bool>",
249 "enable_downloads": "<bool>",
250 "enable_locking": "<bool>",
250 "enable_locking": "<bool>",
251 "enable_statistics": "<bool>",
251 "enable_statistics": "<bool>",
252 },
252 },
253 ...
253 ...
254 ]
254 ]
255 error: null
255 error: null
256 """
256 """
257
257
258 include_secrets = has_superadmin_permission(apiuser)
258 include_secrets = has_superadmin_permission(apiuser)
259 _perms = ('repository.read', 'repository.write', 'repository.admin',)
259 _perms = ('repository.read', 'repository.write', 'repository.admin',)
260 extras = {'user': apiuser}
260 extras = {'user': apiuser}
261
261
262 repo_list = RepoList(
262 repo_list = RepoList(
263 RepoModel().get_all(), perm_set=_perms, extra_kwargs=extras)
263 RepoModel().get_all(), perm_set=_perms, extra_kwargs=extras)
264 return [repo.get_api_data(include_secrets=include_secrets)
264 return [repo.get_api_data(include_secrets=include_secrets)
265 for repo in repo_list]
265 for repo in repo_list]
266
266
267
267
268 @jsonrpc_method()
268 @jsonrpc_method()
269 def get_repo_changeset(request, apiuser, repoid, revision,
269 def get_repo_changeset(request, apiuser, repoid, revision,
270 details=Optional('basic')):
270 details=Optional('basic')):
271 """
271 """
272 Returns information about a changeset.
272 Returns information about a changeset.
273
273
274 Additionally parameters define the amount of details returned by
274 Additionally parameters define the amount of details returned by
275 this function.
275 this function.
276
276
277 This command can only be run using an |authtoken| with admin rights,
277 This command can only be run using an |authtoken| with admin rights,
278 or users with at least read rights to the |repo|.
278 or users with at least read rights to the |repo|.
279
279
280 :param apiuser: This is filled automatically from the |authtoken|.
280 :param apiuser: This is filled automatically from the |authtoken|.
281 :type apiuser: AuthUser
281 :type apiuser: AuthUser
282 :param repoid: The repository name or repository id
282 :param repoid: The repository name or repository id
283 :type repoid: str or int
283 :type repoid: str or int
284 :param revision: revision for which listing should be done
284 :param revision: revision for which listing should be done
285 :type revision: str
285 :type revision: str
286 :param details: details can be 'basic|extended|full' full gives diff
286 :param details: details can be 'basic|extended|full' full gives diff
287 info details like the diff itself, and number of changed files etc.
287 info details like the diff itself, and number of changed files etc.
288 :type details: Optional(str)
288 :type details: Optional(str)
289
289
290 """
290 """
291 repo = get_repo_or_error(repoid)
291 repo = get_repo_or_error(repoid)
292 if not has_superadmin_permission(apiuser):
292 if not has_superadmin_permission(apiuser):
293 _perms = (
293 _perms = (
294 'repository.admin', 'repository.write', 'repository.read',)
294 'repository.admin', 'repository.write', 'repository.read',)
295 has_repo_permissions(apiuser, repoid, repo, _perms)
295 has_repo_permissions(apiuser, repoid, repo, _perms)
296
296
297 changes_details = Optional.extract(details)
297 changes_details = Optional.extract(details)
298 _changes_details_types = ['basic', 'extended', 'full']
298 _changes_details_types = ['basic', 'extended', 'full']
299 if changes_details not in _changes_details_types:
299 if changes_details not in _changes_details_types:
300 raise JSONRPCError(
300 raise JSONRPCError(
301 'ret_type must be one of %s' % (
301 'ret_type must be one of %s' % (
302 ','.join(_changes_details_types)))
302 ','.join(_changes_details_types)))
303
303
304 pre_load = ['author', 'branch', 'date', 'message', 'parents',
304 pre_load = ['author', 'branch', 'date', 'message', 'parents',
305 'status', '_commit', '_file_paths']
305 'status', '_commit', '_file_paths']
306
306
307 try:
307 try:
308 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
308 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
309 except TypeError as e:
309 except TypeError as e:
310 raise JSONRPCError(e.message)
310 raise JSONRPCError(e.message)
311 _cs_json = cs.__json__()
311 _cs_json = cs.__json__()
312 _cs_json['diff'] = build_commit_data(cs, changes_details)
312 _cs_json['diff'] = build_commit_data(cs, changes_details)
313 if changes_details == 'full':
313 if changes_details == 'full':
314 _cs_json['refs'] = {
314 _cs_json['refs'] = {
315 'branches': [cs.branch],
315 'branches': [cs.branch],
316 'bookmarks': getattr(cs, 'bookmarks', []),
316 'bookmarks': getattr(cs, 'bookmarks', []),
317 'tags': cs.tags
317 'tags': cs.tags
318 }
318 }
319 return _cs_json
319 return _cs_json
320
320
321
321
322 @jsonrpc_method()
322 @jsonrpc_method()
323 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
323 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
324 details=Optional('basic')):
324 details=Optional('basic')):
325 """
325 """
326 Returns a set of changesets limited by the number of commits starting
326 Returns a set of commits limited by the number starting
327 from the `start_rev` option.
327 from the `start_rev` option.
328
328
329 Additional parameters define the amount of details returned by this
329 Additional parameters define the amount of details returned by this
330 function.
330 function.
331
331
332 This command can only be run using an |authtoken| with admin rights,
332 This command can only be run using an |authtoken| with admin rights,
333 or users with at least read rights to |repos|.
333 or users with at least read rights to |repos|.
334
334
335 :param apiuser: This is filled automatically from the |authtoken|.
335 :param apiuser: This is filled automatically from the |authtoken|.
336 :type apiuser: AuthUser
336 :type apiuser: AuthUser
337 :param repoid: The repository name or repository ID.
337 :param repoid: The repository name or repository ID.
338 :type repoid: str or int
338 :type repoid: str or int
339 :param start_rev: The starting revision from where to get changesets.
339 :param start_rev: The starting revision from where to get changesets.
340 :type start_rev: str
340 :type start_rev: str
341 :param limit: Limit the number of changesets to this amount
341 :param limit: Limit the number of commits to this amount
342 :type limit: str or int
342 :type limit: str or int
343 :param details: Set the level of detail returned. Valid option are:
343 :param details: Set the level of detail returned. Valid option are:
344 ``basic``, ``extended`` and ``full``.
344 ``basic``, ``extended`` and ``full``.
345 :type details: Optional(str)
345 :type details: Optional(str)
346
346
347 .. note::
347 .. note::
348
348
349 Setting the parameter `details` to the value ``full`` is extensive
349 Setting the parameter `details` to the value ``full`` is extensive
350 and returns details like the diff itself, and the number
350 and returns details like the diff itself, and the number
351 of changed files.
351 of changed files.
352
352
353 """
353 """
354 repo = get_repo_or_error(repoid)
354 repo = get_repo_or_error(repoid)
355 if not has_superadmin_permission(apiuser):
355 if not has_superadmin_permission(apiuser):
356 _perms = (
356 _perms = (
357 'repository.admin', 'repository.write', 'repository.read',)
357 'repository.admin', 'repository.write', 'repository.read',)
358 has_repo_permissions(apiuser, repoid, repo, _perms)
358 has_repo_permissions(apiuser, repoid, repo, _perms)
359
359
360 changes_details = Optional.extract(details)
360 changes_details = Optional.extract(details)
361 _changes_details_types = ['basic', 'extended', 'full']
361 _changes_details_types = ['basic', 'extended', 'full']
362 if changes_details not in _changes_details_types:
362 if changes_details not in _changes_details_types:
363 raise JSONRPCError(
363 raise JSONRPCError(
364 'ret_type must be one of %s' % (
364 'ret_type must be one of %s' % (
365 ','.join(_changes_details_types)))
365 ','.join(_changes_details_types)))
366
366
367 limit = int(limit)
367 limit = int(limit)
368 pre_load = ['author', 'branch', 'date', 'message', 'parents',
368 pre_load = ['author', 'branch', 'date', 'message', 'parents',
369 'status', '_commit', '_file_paths']
369 'status', '_commit', '_file_paths']
370
370
371 vcs_repo = repo.scm_instance()
371 vcs_repo = repo.scm_instance()
372 # SVN needs a special case to distinguish its index and commit id
372 # SVN needs a special case to distinguish its index and commit id
373 if vcs_repo.alias == 'svn' and (start_rev == '0'):
373 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
374 start_rev = vcs_repo.commit_ids[0]
374 start_rev = vcs_repo.commit_ids[0]
375
375
376 try:
376 try:
377 commits = repo.scm_instance().get_commits(
377 commits = vcs_repo.get_commits(
378 start_id=start_rev, pre_load=pre_load)
378 start_id=start_rev, pre_load=pre_load)
379 except TypeError as e:
379 except TypeError as e:
380 raise JSONRPCError(e.message)
380 raise JSONRPCError(e.message)
381 except Exception:
382 log.exception('Fetching of commits failed')
383 raise JSONRPCError('Error occurred during commit fetching')
381
384
382 ret = []
385 ret = []
383 for cnt, commit in enumerate(commits):
386 for cnt, commit in enumerate(commits):
384 if cnt >= limit != -1:
387 if cnt >= limit != -1:
385 break
388 break
386 _cs_json = commit.__json__()
389 _cs_json = commit.__json__()
387 _cs_json['diff'] = build_commit_data(commit, changes_details)
390 _cs_json['diff'] = build_commit_data(commit, changes_details)
388 if changes_details == 'full':
391 if changes_details == 'full':
389 _cs_json['refs'] = {
392 _cs_json['refs'] = {
390 'branches': [commit.branch],
393 'branches': [commit.branch],
391 'bookmarks': getattr(commit, 'bookmarks', []),
394 'bookmarks': getattr(commit, 'bookmarks', []),
392 'tags': commit.tags
395 'tags': commit.tags
393 }
396 }
394 ret.append(_cs_json)
397 ret.append(_cs_json)
395 return ret
398 return ret
396
399
397
400
398 @jsonrpc_method()
401 @jsonrpc_method()
399 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
402 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
400 ret_type=Optional('all'), details=Optional('basic')):
403 ret_type=Optional('all'), details=Optional('basic')):
401 """
404 """
402 Returns a list of nodes and children in a flat list for a given
405 Returns a list of nodes and children in a flat list for a given
403 path at given revision.
406 path at given revision.
404
407
405 It's possible to specify ret_type to show only `files` or `dirs`.
408 It's possible to specify ret_type to show only `files` or `dirs`.
406
409
407 This command can only be run using an |authtoken| with admin rights,
410 This command can only be run using an |authtoken| with admin rights,
408 or users with at least read rights to |repos|.
411 or users with at least read rights to |repos|.
409
412
410 :param apiuser: This is filled automatically from the |authtoken|.
413 :param apiuser: This is filled automatically from the |authtoken|.
411 :type apiuser: AuthUser
414 :type apiuser: AuthUser
412 :param repoid: The repository name or repository ID.
415 :param repoid: The repository name or repository ID.
413 :type repoid: str or int
416 :type repoid: str or int
414 :param revision: The revision for which listing should be done.
417 :param revision: The revision for which listing should be done.
415 :type revision: str
418 :type revision: str
416 :param root_path: The path from which to start displaying.
419 :param root_path: The path from which to start displaying.
417 :type root_path: str
420 :type root_path: str
418 :param ret_type: Set the return type. Valid options are
421 :param ret_type: Set the return type. Valid options are
419 ``all`` (default), ``files`` and ``dirs``.
422 ``all`` (default), ``files`` and ``dirs``.
420 :type ret_type: Optional(str)
423 :type ret_type: Optional(str)
421 :param details: Returns extended information about nodes, such as
424 :param details: Returns extended information about nodes, such as
422 md5, binary, and or content. The valid options are ``basic`` and
425 md5, binary, and or content. The valid options are ``basic`` and
423 ``full``.
426 ``full``.
424 :type details: Optional(str)
427 :type details: Optional(str)
425
428
426 Example output:
429 Example output:
427
430
428 .. code-block:: bash
431 .. code-block:: bash
429
432
430 id : <id_given_in_input>
433 id : <id_given_in_input>
431 result: [
434 result: [
432 {
435 {
433 "name" : "<name>"
436 "name" : "<name>"
434 "type" : "<type>",
437 "type" : "<type>",
435 "binary": "<true|false>" (only in extended mode)
438 "binary": "<true|false>" (only in extended mode)
436 "md5" : "<md5 of file content>" (only in extended mode)
439 "md5" : "<md5 of file content>" (only in extended mode)
437 },
440 },
438 ...
441 ...
439 ]
442 ]
440 error: null
443 error: null
441 """
444 """
442
445
443 repo = get_repo_or_error(repoid)
446 repo = get_repo_or_error(repoid)
444 if not has_superadmin_permission(apiuser):
447 if not has_superadmin_permission(apiuser):
445 _perms = (
448 _perms = (
446 'repository.admin', 'repository.write', 'repository.read',)
449 'repository.admin', 'repository.write', 'repository.read',)
447 has_repo_permissions(apiuser, repoid, repo, _perms)
450 has_repo_permissions(apiuser, repoid, repo, _perms)
448
451
449 ret_type = Optional.extract(ret_type)
452 ret_type = Optional.extract(ret_type)
450 details = Optional.extract(details)
453 details = Optional.extract(details)
451 _extended_types = ['basic', 'full']
454 _extended_types = ['basic', 'full']
452 if details not in _extended_types:
455 if details not in _extended_types:
453 raise JSONRPCError(
456 raise JSONRPCError(
454 'ret_type must be one of %s' % (','.join(_extended_types)))
457 'ret_type must be one of %s' % (','.join(_extended_types)))
455 extended_info = False
458 extended_info = False
456 content = False
459 content = False
457 if details == 'basic':
460 if details == 'basic':
458 extended_info = True
461 extended_info = True
459
462
460 if details == 'full':
463 if details == 'full':
461 extended_info = content = True
464 extended_info = content = True
462
465
463 _map = {}
466 _map = {}
464 try:
467 try:
465 # check if repo is not empty by any chance, skip quicker if it is.
468 # check if repo is not empty by any chance, skip quicker if it is.
466 _scm = repo.scm_instance()
469 _scm = repo.scm_instance()
467 if _scm.is_empty():
470 if _scm.is_empty():
468 return []
471 return []
469
472
470 _d, _f = ScmModel().get_nodes(
473 _d, _f = ScmModel().get_nodes(
471 repo, revision, root_path, flat=False,
474 repo, revision, root_path, flat=False,
472 extended_info=extended_info, content=content)
475 extended_info=extended_info, content=content)
473 _map = {
476 _map = {
474 'all': _d + _f,
477 'all': _d + _f,
475 'files': _f,
478 'files': _f,
476 'dirs': _d,
479 'dirs': _d,
477 }
480 }
478 return _map[ret_type]
481 return _map[ret_type]
479 except KeyError:
482 except KeyError:
480 raise JSONRPCError(
483 raise JSONRPCError(
481 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
484 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
482 except Exception:
485 except Exception:
483 log.exception("Exception occurred while trying to get repo nodes")
486 log.exception("Exception occurred while trying to get repo nodes")
484 raise JSONRPCError(
487 raise JSONRPCError(
485 'failed to get repo: `%s` nodes' % repo.repo_name
488 'failed to get repo: `%s` nodes' % repo.repo_name
486 )
489 )
487
490
488
491
489 @jsonrpc_method()
492 @jsonrpc_method()
490 def get_repo_refs(request, apiuser, repoid):
493 def get_repo_refs(request, apiuser, repoid):
491 """
494 """
492 Returns a dictionary of current references. It returns
495 Returns a dictionary of current references. It returns
493 bookmarks, branches, closed_branches, and tags for given repository
496 bookmarks, branches, closed_branches, and tags for given repository
494
497
495 It's possible to specify ret_type to show only `files` or `dirs`.
498 It's possible to specify ret_type to show only `files` or `dirs`.
496
499
497 This command can only be run using an |authtoken| with admin rights,
500 This command can only be run using an |authtoken| with admin rights,
498 or users with at least read rights to |repos|.
501 or users with at least read rights to |repos|.
499
502
500 :param apiuser: This is filled automatically from the |authtoken|.
503 :param apiuser: This is filled automatically from the |authtoken|.
501 :type apiuser: AuthUser
504 :type apiuser: AuthUser
502 :param repoid: The repository name or repository ID.
505 :param repoid: The repository name or repository ID.
503 :type repoid: str or int
506 :type repoid: str or int
504
507
505 Example output:
508 Example output:
506
509
507 .. code-block:: bash
510 .. code-block:: bash
508
511
509 id : <id_given_in_input>
512 id : <id_given_in_input>
510 result: [
513 result: [
511 TODO...
514 TODO...
512 ]
515 ]
513 error: null
516 error: null
514 """
517 """
515
518
516 repo = get_repo_or_error(repoid)
519 repo = get_repo_or_error(repoid)
517 if not has_superadmin_permission(apiuser):
520 if not has_superadmin_permission(apiuser):
518 _perms = ('repository.admin', 'repository.write', 'repository.read',)
521 _perms = ('repository.admin', 'repository.write', 'repository.read',)
519 has_repo_permissions(apiuser, repoid, repo, _perms)
522 has_repo_permissions(apiuser, repoid, repo, _perms)
520
523
521 try:
524 try:
522 # check if repo is not empty by any chance, skip quicker if it is.
525 # check if repo is not empty by any chance, skip quicker if it is.
523 vcs_instance = repo.scm_instance()
526 vcs_instance = repo.scm_instance()
524 refs = vcs_instance.refs()
527 refs = vcs_instance.refs()
525 return refs
528 return refs
526 except Exception:
529 except Exception:
527 log.exception("Exception occurred while trying to get repo refs")
530 log.exception("Exception occurred while trying to get repo refs")
528 raise JSONRPCError(
531 raise JSONRPCError(
529 'failed to get repo: `%s` references' % repo.repo_name
532 'failed to get repo: `%s` references' % repo.repo_name
530 )
533 )
531
534
532
535
533 @jsonrpc_method()
536 @jsonrpc_method()
534 def create_repo(request, apiuser, repo_name, repo_type,
537 def create_repo(request, apiuser, repo_name, repo_type,
535 owner=Optional(OAttr('apiuser')), description=Optional(''),
538 owner=Optional(OAttr('apiuser')), description=Optional(''),
536 private=Optional(False), clone_uri=Optional(None),
539 private=Optional(False), clone_uri=Optional(None),
537 landing_rev=Optional('rev:tip'),
540 landing_rev=Optional('rev:tip'),
538 enable_statistics=Optional(False),
541 enable_statistics=Optional(False),
539 enable_locking=Optional(False),
542 enable_locking=Optional(False),
540 enable_downloads=Optional(False),
543 enable_downloads=Optional(False),
541 copy_permissions=Optional(False)):
544 copy_permissions=Optional(False)):
542 """
545 """
543 Creates a repository.
546 Creates a repository.
544
547
545 * If the repository name contains "/", all the required repository
548 * If the repository name contains "/", all the required repository
546 groups will be created.
549 groups will be created.
547
550
548 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
551 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
549 (with "foo" as parent). It will also create the "baz" repository
552 (with "foo" as parent). It will also create the "baz" repository
550 with "bar" as |repo| group.
553 with "bar" as |repo| group.
551
554
552 This command can only be run using an |authtoken| with at least
555 This command can only be run using an |authtoken| with at least
553 write permissions to the |repo|.
556 write permissions to the |repo|.
554
557
555 :param apiuser: This is filled automatically from the |authtoken|.
558 :param apiuser: This is filled automatically from the |authtoken|.
556 :type apiuser: AuthUser
559 :type apiuser: AuthUser
557 :param repo_name: Set the repository name.
560 :param repo_name: Set the repository name.
558 :type repo_name: str
561 :type repo_name: str
559 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
562 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
560 :type repo_type: str
563 :type repo_type: str
561 :param owner: user_id or username
564 :param owner: user_id or username
562 :type owner: Optional(str)
565 :type owner: Optional(str)
563 :param description: Set the repository description.
566 :param description: Set the repository description.
564 :type description: Optional(str)
567 :type description: Optional(str)
565 :param private:
568 :param private:
566 :type private: bool
569 :type private: bool
567 :param clone_uri:
570 :param clone_uri:
568 :type clone_uri: str
571 :type clone_uri: str
569 :param landing_rev: <rev_type>:<rev>
572 :param landing_rev: <rev_type>:<rev>
570 :type landing_rev: str
573 :type landing_rev: str
571 :param enable_locking:
574 :param enable_locking:
572 :type enable_locking: bool
575 :type enable_locking: bool
573 :param enable_downloads:
576 :param enable_downloads:
574 :type enable_downloads: bool
577 :type enable_downloads: bool
575 :param enable_statistics:
578 :param enable_statistics:
576 :type enable_statistics: bool
579 :type enable_statistics: bool
577 :param copy_permissions: Copy permission from group in which the
580 :param copy_permissions: Copy permission from group in which the
578 repository is being created.
581 repository is being created.
579 :type copy_permissions: bool
582 :type copy_permissions: bool
580
583
581
584
582 Example output:
585 Example output:
583
586
584 .. code-block:: bash
587 .. code-block:: bash
585
588
586 id : <id_given_in_input>
589 id : <id_given_in_input>
587 result: {
590 result: {
588 "msg": "Created new repository `<reponame>`",
591 "msg": "Created new repository `<reponame>`",
589 "success": true,
592 "success": true,
590 "task": "<celery task id or None if done sync>"
593 "task": "<celery task id or None if done sync>"
591 }
594 }
592 error: null
595 error: null
593
596
594
597
595 Example error output:
598 Example error output:
596
599
597 .. code-block:: bash
600 .. code-block:: bash
598
601
599 id : <id_given_in_input>
602 id : <id_given_in_input>
600 result : null
603 result : null
601 error : {
604 error : {
602 'failed to create repository `<repo_name>`
605 'failed to create repository `<repo_name>`
603 }
606 }
604
607
605 """
608 """
606 schema = RepoSchema()
609 schema = RepoSchema()
607 try:
610 try:
608 data = schema.deserialize({
611 data = schema.deserialize({
609 'repo_name': repo_name
612 'repo_name': repo_name
610 })
613 })
611 except colander.Invalid as e:
614 except colander.Invalid as e:
612 raise JSONRPCError("Validation failed: %s" % (e.asdict(),))
615 raise JSONRPCError("Validation failed: %s" % (e.asdict(),))
613 repo_name = data['repo_name']
616 repo_name = data['repo_name']
614
617
615 (repo_name_cleaned,
618 (repo_name_cleaned,
616 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(
619 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(
617 repo_name)
620 repo_name)
618
621
619 if not HasPermissionAnyApi(
622 if not HasPermissionAnyApi(
620 'hg.admin', 'hg.create.repository')(user=apiuser):
623 'hg.admin', 'hg.create.repository')(user=apiuser):
621 # check if we have admin permission for this repo group if given !
624 # check if we have admin permission for this repo group if given !
622
625
623 if parent_group_name:
626 if parent_group_name:
624 repogroupid = parent_group_name
627 repogroupid = parent_group_name
625 repo_group = get_repo_group_or_error(parent_group_name)
628 repo_group = get_repo_group_or_error(parent_group_name)
626
629
627 _perms = ('group.admin',)
630 _perms = ('group.admin',)
628 if not HasRepoGroupPermissionAnyApi(*_perms)(
631 if not HasRepoGroupPermissionAnyApi(*_perms)(
629 user=apiuser, group_name=repo_group.group_name):
632 user=apiuser, group_name=repo_group.group_name):
630 raise JSONRPCError(
633 raise JSONRPCError(
631 'repository group `%s` does not exist' % (
634 'repository group `%s` does not exist' % (
632 repogroupid,))
635 repogroupid,))
633 else:
636 else:
634 raise JSONRPCForbidden()
637 raise JSONRPCForbidden()
635
638
636 if not has_superadmin_permission(apiuser):
639 if not has_superadmin_permission(apiuser):
637 if not isinstance(owner, Optional):
640 if not isinstance(owner, Optional):
638 # forbid setting owner for non-admins
641 # forbid setting owner for non-admins
639 raise JSONRPCError(
642 raise JSONRPCError(
640 'Only RhodeCode admin can specify `owner` param')
643 'Only RhodeCode admin can specify `owner` param')
641
644
642 if isinstance(owner, Optional):
645 if isinstance(owner, Optional):
643 owner = apiuser.user_id
646 owner = apiuser.user_id
644
647
645 owner = get_user_or_error(owner)
648 owner = get_user_or_error(owner)
646
649
647 if RepoModel().get_by_repo_name(repo_name):
650 if RepoModel().get_by_repo_name(repo_name):
648 raise JSONRPCError("repo `%s` already exist" % repo_name)
651 raise JSONRPCError("repo `%s` already exist" % repo_name)
649
652
650 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
653 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
651 if isinstance(private, Optional):
654 if isinstance(private, Optional):
652 private = defs.get('repo_private') or Optional.extract(private)
655 private = defs.get('repo_private') or Optional.extract(private)
653 if isinstance(repo_type, Optional):
656 if isinstance(repo_type, Optional):
654 repo_type = defs.get('repo_type')
657 repo_type = defs.get('repo_type')
655 if isinstance(enable_statistics, Optional):
658 if isinstance(enable_statistics, Optional):
656 enable_statistics = defs.get('repo_enable_statistics')
659 enable_statistics = defs.get('repo_enable_statistics')
657 if isinstance(enable_locking, Optional):
660 if isinstance(enable_locking, Optional):
658 enable_locking = defs.get('repo_enable_locking')
661 enable_locking = defs.get('repo_enable_locking')
659 if isinstance(enable_downloads, Optional):
662 if isinstance(enable_downloads, Optional):
660 enable_downloads = defs.get('repo_enable_downloads')
663 enable_downloads = defs.get('repo_enable_downloads')
661
664
662 clone_uri = Optional.extract(clone_uri)
665 clone_uri = Optional.extract(clone_uri)
663 description = Optional.extract(description)
666 description = Optional.extract(description)
664 landing_rev = Optional.extract(landing_rev)
667 landing_rev = Optional.extract(landing_rev)
665 copy_permissions = Optional.extract(copy_permissions)
668 copy_permissions = Optional.extract(copy_permissions)
666
669
667 try:
670 try:
668 # create structure of groups and return the last group
671 # create structure of groups and return the last group
669 repo_group = map_groups(repo_name)
672 repo_group = map_groups(repo_name)
670 data = {
673 data = {
671 'repo_name': repo_name_cleaned,
674 'repo_name': repo_name_cleaned,
672 'repo_name_full': repo_name,
675 'repo_name_full': repo_name,
673 'repo_type': repo_type,
676 'repo_type': repo_type,
674 'repo_description': description,
677 'repo_description': description,
675 'owner': owner,
678 'owner': owner,
676 'repo_private': private,
679 'repo_private': private,
677 'clone_uri': clone_uri,
680 'clone_uri': clone_uri,
678 'repo_group': repo_group.group_id if repo_group else None,
681 'repo_group': repo_group.group_id if repo_group else None,
679 'repo_landing_rev': landing_rev,
682 'repo_landing_rev': landing_rev,
680 'enable_statistics': enable_statistics,
683 'enable_statistics': enable_statistics,
681 'enable_locking': enable_locking,
684 'enable_locking': enable_locking,
682 'enable_downloads': enable_downloads,
685 'enable_downloads': enable_downloads,
683 'repo_copy_permissions': copy_permissions,
686 'repo_copy_permissions': copy_permissions,
684 }
687 }
685
688
686 if repo_type not in BACKENDS.keys():
689 if repo_type not in BACKENDS.keys():
687 raise Exception("Invalid backend type %s" % repo_type)
690 raise Exception("Invalid backend type %s" % repo_type)
688 task = RepoModel().create(form_data=data, cur_user=owner)
691 task = RepoModel().create(form_data=data, cur_user=owner)
689 from celery.result import BaseAsyncResult
692 from celery.result import BaseAsyncResult
690 task_id = None
693 task_id = None
691 if isinstance(task, BaseAsyncResult):
694 if isinstance(task, BaseAsyncResult):
692 task_id = task.task_id
695 task_id = task.task_id
693 # no commit, it's done in RepoModel, or async via celery
696 # no commit, it's done in RepoModel, or async via celery
694 return {
697 return {
695 'msg': "Created new repository `%s`" % (repo_name,),
698 'msg': "Created new repository `%s`" % (repo_name,),
696 'success': True, # cannot return the repo data here since fork
699 'success': True, # cannot return the repo data here since fork
697 # cann be done async
700 # cann be done async
698 'task': task_id
701 'task': task_id
699 }
702 }
700 except Exception:
703 except Exception:
701 log.exception(
704 log.exception(
702 u"Exception while trying to create the repository %s",
705 u"Exception while trying to create the repository %s",
703 repo_name)
706 repo_name)
704 raise JSONRPCError(
707 raise JSONRPCError(
705 'failed to create repository `%s`' % (repo_name,))
708 'failed to create repository `%s`' % (repo_name,))
706
709
707
710
708 @jsonrpc_method()
711 @jsonrpc_method()
709 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
712 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
710 description=Optional('')):
713 description=Optional('')):
711 """
714 """
712 Adds an extra field to a repository.
715 Adds an extra field to a repository.
713
716
714 This command can only be run using an |authtoken| with at least
717 This command can only be run using an |authtoken| with at least
715 write permissions to the |repo|.
718 write permissions to the |repo|.
716
719
717 :param apiuser: This is filled automatically from the |authtoken|.
720 :param apiuser: This is filled automatically from the |authtoken|.
718 :type apiuser: AuthUser
721 :type apiuser: AuthUser
719 :param repoid: Set the repository name or repository id.
722 :param repoid: Set the repository name or repository id.
720 :type repoid: str or int
723 :type repoid: str or int
721 :param key: Create a unique field key for this repository.
724 :param key: Create a unique field key for this repository.
722 :type key: str
725 :type key: str
723 :param label:
726 :param label:
724 :type label: Optional(str)
727 :type label: Optional(str)
725 :param description:
728 :param description:
726 :type description: Optional(str)
729 :type description: Optional(str)
727 """
730 """
728 repo = get_repo_or_error(repoid)
731 repo = get_repo_or_error(repoid)
729 if not has_superadmin_permission(apiuser):
732 if not has_superadmin_permission(apiuser):
730 _perms = ('repository.admin',)
733 _perms = ('repository.admin',)
731 has_repo_permissions(apiuser, repoid, repo, _perms)
734 has_repo_permissions(apiuser, repoid, repo, _perms)
732
735
733 label = Optional.extract(label) or key
736 label = Optional.extract(label) or key
734 description = Optional.extract(description)
737 description = Optional.extract(description)
735
738
736 field = RepositoryField.get_by_key_name(key, repo)
739 field = RepositoryField.get_by_key_name(key, repo)
737 if field:
740 if field:
738 raise JSONRPCError('Field with key '
741 raise JSONRPCError('Field with key '
739 '`%s` exists for repo `%s`' % (key, repoid))
742 '`%s` exists for repo `%s`' % (key, repoid))
740
743
741 try:
744 try:
742 RepoModel().add_repo_field(repo, key, field_label=label,
745 RepoModel().add_repo_field(repo, key, field_label=label,
743 field_desc=description)
746 field_desc=description)
744 Session().commit()
747 Session().commit()
745 return {
748 return {
746 'msg': "Added new repository field `%s`" % (key,),
749 'msg': "Added new repository field `%s`" % (key,),
747 'success': True,
750 'success': True,
748 }
751 }
749 except Exception:
752 except Exception:
750 log.exception("Exception occurred while trying to add field to repo")
753 log.exception("Exception occurred while trying to add field to repo")
751 raise JSONRPCError(
754 raise JSONRPCError(
752 'failed to create new field for repository `%s`' % (repoid,))
755 'failed to create new field for repository `%s`' % (repoid,))
753
756
754
757
755 @jsonrpc_method()
758 @jsonrpc_method()
756 def remove_field_from_repo(request, apiuser, repoid, key):
759 def remove_field_from_repo(request, apiuser, repoid, key):
757 """
760 """
758 Removes an extra field from a repository.
761 Removes an extra field from a repository.
759
762
760 This command can only be run using an |authtoken| with at least
763 This command can only be run using an |authtoken| with at least
761 write permissions to the |repo|.
764 write permissions to the |repo|.
762
765
763 :param apiuser: This is filled automatically from the |authtoken|.
766 :param apiuser: This is filled automatically from the |authtoken|.
764 :type apiuser: AuthUser
767 :type apiuser: AuthUser
765 :param repoid: Set the repository name or repository ID.
768 :param repoid: Set the repository name or repository ID.
766 :type repoid: str or int
769 :type repoid: str or int
767 :param key: Set the unique field key for this repository.
770 :param key: Set the unique field key for this repository.
768 :type key: str
771 :type key: str
769 """
772 """
770
773
771 repo = get_repo_or_error(repoid)
774 repo = get_repo_or_error(repoid)
772 if not has_superadmin_permission(apiuser):
775 if not has_superadmin_permission(apiuser):
773 _perms = ('repository.admin',)
776 _perms = ('repository.admin',)
774 has_repo_permissions(apiuser, repoid, repo, _perms)
777 has_repo_permissions(apiuser, repoid, repo, _perms)
775
778
776 field = RepositoryField.get_by_key_name(key, repo)
779 field = RepositoryField.get_by_key_name(key, repo)
777 if not field:
780 if not field:
778 raise JSONRPCError('Field with key `%s` does not '
781 raise JSONRPCError('Field with key `%s` does not '
779 'exists for repo `%s`' % (key, repoid))
782 'exists for repo `%s`' % (key, repoid))
780
783
781 try:
784 try:
782 RepoModel().delete_repo_field(repo, field_key=key)
785 RepoModel().delete_repo_field(repo, field_key=key)
783 Session().commit()
786 Session().commit()
784 return {
787 return {
785 'msg': "Deleted repository field `%s`" % (key,),
788 'msg': "Deleted repository field `%s`" % (key,),
786 'success': True,
789 'success': True,
787 }
790 }
788 except Exception:
791 except Exception:
789 log.exception(
792 log.exception(
790 "Exception occurred while trying to delete field from repo")
793 "Exception occurred while trying to delete field from repo")
791 raise JSONRPCError(
794 raise JSONRPCError(
792 'failed to delete field for repository `%s`' % (repoid,))
795 'failed to delete field for repository `%s`' % (repoid,))
793
796
794
797
795 @jsonrpc_method()
798 @jsonrpc_method()
796 def update_repo(request, apiuser, repoid, name=Optional(None),
799 def update_repo(request, apiuser, repoid, name=Optional(None),
797 owner=Optional(OAttr('apiuser')),
800 owner=Optional(OAttr('apiuser')),
798 group=Optional(None),
801 group=Optional(None),
799 fork_of=Optional(None),
802 fork_of=Optional(None),
800 description=Optional(''), private=Optional(False),
803 description=Optional(''), private=Optional(False),
801 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
804 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
802 enable_statistics=Optional(False),
805 enable_statistics=Optional(False),
803 enable_locking=Optional(False),
806 enable_locking=Optional(False),
804 enable_downloads=Optional(False),
807 enable_downloads=Optional(False),
805 fields=Optional('')):
808 fields=Optional('')):
806 """
809 """
807 Updates a repository with the given information.
810 Updates a repository with the given information.
808
811
809 This command can only be run using an |authtoken| with at least
812 This command can only be run using an |authtoken| with at least
810 write permissions to the |repo|.
813 write permissions to the |repo|.
811
814
812 :param apiuser: This is filled automatically from the |authtoken|.
815 :param apiuser: This is filled automatically from the |authtoken|.
813 :type apiuser: AuthUser
816 :type apiuser: AuthUser
814 :param repoid: repository name or repository ID.
817 :param repoid: repository name or repository ID.
815 :type repoid: str or int
818 :type repoid: str or int
816 :param name: Update the |repo| name.
819 :param name: Update the |repo| name.
817 :type name: str
820 :type name: str
818 :param owner: Set the |repo| owner.
821 :param owner: Set the |repo| owner.
819 :type owner: str
822 :type owner: str
820 :param group: Set the |repo| group the |repo| belongs to.
823 :param group: Set the |repo| group the |repo| belongs to.
821 :type group: str
824 :type group: str
822 :param fork_of: Set the master |repo| name.
825 :param fork_of: Set the master |repo| name.
823 :type fork_of: str
826 :type fork_of: str
824 :param description: Update the |repo| description.
827 :param description: Update the |repo| description.
825 :type description: str
828 :type description: str
826 :param private: Set the |repo| as private. (True | False)
829 :param private: Set the |repo| as private. (True | False)
827 :type private: bool
830 :type private: bool
828 :param clone_uri: Update the |repo| clone URI.
831 :param clone_uri: Update the |repo| clone URI.
829 :type clone_uri: str
832 :type clone_uri: str
830 :param landing_rev: Set the |repo| landing revision. Default is
833 :param landing_rev: Set the |repo| landing revision. Default is
831 ``tip``.
834 ``tip``.
832 :type landing_rev: str
835 :type landing_rev: str
833 :param enable_statistics: Enable statistics on the |repo|,
836 :param enable_statistics: Enable statistics on the |repo|,
834 (True | False).
837 (True | False).
835 :type enable_statistics: bool
838 :type enable_statistics: bool
836 :param enable_locking: Enable |repo| locking.
839 :param enable_locking: Enable |repo| locking.
837 :type enable_locking: bool
840 :type enable_locking: bool
838 :param enable_downloads: Enable downloads from the |repo|,
841 :param enable_downloads: Enable downloads from the |repo|,
839 (True | False).
842 (True | False).
840 :type enable_downloads: bool
843 :type enable_downloads: bool
841 :param fields: Add extra fields to the |repo|. Use the following
844 :param fields: Add extra fields to the |repo|. Use the following
842 example format: ``field_key=field_val,field_key2=fieldval2``.
845 example format: ``field_key=field_val,field_key2=fieldval2``.
843 Escape ', ' with \,
846 Escape ', ' with \,
844 :type fields: str
847 :type fields: str
845 """
848 """
846 repo = get_repo_or_error(repoid)
849 repo = get_repo_or_error(repoid)
847 include_secrets = False
850 include_secrets = False
848 if has_superadmin_permission(apiuser):
851 if has_superadmin_permission(apiuser):
849 include_secrets = True
852 include_secrets = True
850 else:
853 else:
851 _perms = ('repository.admin',)
854 _perms = ('repository.admin',)
852 has_repo_permissions(apiuser, repoid, repo, _perms)
855 has_repo_permissions(apiuser, repoid, repo, _perms)
853
856
854 updates = {
857 updates = {
855 # update function requires this.
858 # update function requires this.
856 'repo_name': repo.just_name
859 'repo_name': repo.just_name
857 }
860 }
858 repo_group = group
861 repo_group = group
859 if not isinstance(repo_group, Optional):
862 if not isinstance(repo_group, Optional):
860 repo_group = get_repo_group_or_error(repo_group)
863 repo_group = get_repo_group_or_error(repo_group)
861 repo_group = repo_group.group_id
864 repo_group = repo_group.group_id
862
865
863 repo_fork_of = fork_of
866 repo_fork_of = fork_of
864 if not isinstance(repo_fork_of, Optional):
867 if not isinstance(repo_fork_of, Optional):
865 repo_fork_of = get_repo_or_error(repo_fork_of)
868 repo_fork_of = get_repo_or_error(repo_fork_of)
866 repo_fork_of = repo_fork_of.repo_id
869 repo_fork_of = repo_fork_of.repo_id
867
870
868 try:
871 try:
869 store_update(updates, name, 'repo_name')
872 store_update(updates, name, 'repo_name')
870 store_update(updates, repo_group, 'repo_group')
873 store_update(updates, repo_group, 'repo_group')
871 store_update(updates, repo_fork_of, 'fork_id')
874 store_update(updates, repo_fork_of, 'fork_id')
872 store_update(updates, owner, 'user')
875 store_update(updates, owner, 'user')
873 store_update(updates, description, 'repo_description')
876 store_update(updates, description, 'repo_description')
874 store_update(updates, private, 'repo_private')
877 store_update(updates, private, 'repo_private')
875 store_update(updates, clone_uri, 'clone_uri')
878 store_update(updates, clone_uri, 'clone_uri')
876 store_update(updates, landing_rev, 'repo_landing_rev')
879 store_update(updates, landing_rev, 'repo_landing_rev')
877 store_update(updates, enable_statistics, 'repo_enable_statistics')
880 store_update(updates, enable_statistics, 'repo_enable_statistics')
878 store_update(updates, enable_locking, 'repo_enable_locking')
881 store_update(updates, enable_locking, 'repo_enable_locking')
879 store_update(updates, enable_downloads, 'repo_enable_downloads')
882 store_update(updates, enable_downloads, 'repo_enable_downloads')
880
883
881 # extra fields
884 # extra fields
882 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
885 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
883 if fields:
886 if fields:
884 updates.update(fields)
887 updates.update(fields)
885
888
886 RepoModel().update(repo, **updates)
889 RepoModel().update(repo, **updates)
887 Session().commit()
890 Session().commit()
888 return {
891 return {
889 'msg': 'updated repo ID:%s %s' % (
892 'msg': 'updated repo ID:%s %s' % (
890 repo.repo_id, repo.repo_name),
893 repo.repo_id, repo.repo_name),
891 'repository': repo.get_api_data(
894 'repository': repo.get_api_data(
892 include_secrets=include_secrets)
895 include_secrets=include_secrets)
893 }
896 }
894 except Exception:
897 except Exception:
895 log.exception(
898 log.exception(
896 u"Exception while trying to update the repository %s",
899 u"Exception while trying to update the repository %s",
897 repoid)
900 repoid)
898 raise JSONRPCError('failed to update repo `%s`' % repoid)
901 raise JSONRPCError('failed to update repo `%s`' % repoid)
899
902
900
903
901 @jsonrpc_method()
904 @jsonrpc_method()
902 def fork_repo(request, apiuser, repoid, fork_name,
905 def fork_repo(request, apiuser, repoid, fork_name,
903 owner=Optional(OAttr('apiuser')),
906 owner=Optional(OAttr('apiuser')),
904 description=Optional(''), copy_permissions=Optional(False),
907 description=Optional(''), copy_permissions=Optional(False),
905 private=Optional(False), landing_rev=Optional('rev:tip')):
908 private=Optional(False), landing_rev=Optional('rev:tip')):
906 """
909 """
907 Creates a fork of the specified |repo|.
910 Creates a fork of the specified |repo|.
908
911
909 * If using |RCE| with Celery this will immediately return a success
912 * If using |RCE| with Celery this will immediately return a success
910 message, even though the fork will be created asynchronously.
913 message, even though the fork will be created asynchronously.
911
914
912 This command can only be run using an |authtoken| with fork
915 This command can only be run using an |authtoken| with fork
913 permissions on the |repo|.
916 permissions on the |repo|.
914
917
915 :param apiuser: This is filled automatically from the |authtoken|.
918 :param apiuser: This is filled automatically from the |authtoken|.
916 :type apiuser: AuthUser
919 :type apiuser: AuthUser
917 :param repoid: Set repository name or repository ID.
920 :param repoid: Set repository name or repository ID.
918 :type repoid: str or int
921 :type repoid: str or int
919 :param fork_name: Set the fork name.
922 :param fork_name: Set the fork name.
920 :type fork_name: str
923 :type fork_name: str
921 :param owner: Set the fork owner.
924 :param owner: Set the fork owner.
922 :type owner: str
925 :type owner: str
923 :param description: Set the fork descripton.
926 :param description: Set the fork descripton.
924 :type description: str
927 :type description: str
925 :param copy_permissions: Copy permissions from parent |repo|. The
928 :param copy_permissions: Copy permissions from parent |repo|. The
926 default is False.
929 default is False.
927 :type copy_permissions: bool
930 :type copy_permissions: bool
928 :param private: Make the fork private. The default is False.
931 :param private: Make the fork private. The default is False.
929 :type private: bool
932 :type private: bool
930 :param landing_rev: Set the landing revision. The default is tip.
933 :param landing_rev: Set the landing revision. The default is tip.
931
934
932 Example output:
935 Example output:
933
936
934 .. code-block:: bash
937 .. code-block:: bash
935
938
936 id : <id_for_response>
939 id : <id_for_response>
937 api_key : "<api_key>"
940 api_key : "<api_key>"
938 args: {
941 args: {
939 "repoid" : "<reponame or repo_id>",
942 "repoid" : "<reponame or repo_id>",
940 "fork_name": "<forkname>",
943 "fork_name": "<forkname>",
941 "owner": "<username or user_id = Optional(=apiuser)>",
944 "owner": "<username or user_id = Optional(=apiuser)>",
942 "description": "<description>",
945 "description": "<description>",
943 "copy_permissions": "<bool>",
946 "copy_permissions": "<bool>",
944 "private": "<bool>",
947 "private": "<bool>",
945 "landing_rev": "<landing_rev>"
948 "landing_rev": "<landing_rev>"
946 }
949 }
947
950
948 Example error output:
951 Example error output:
949
952
950 .. code-block:: bash
953 .. code-block:: bash
951
954
952 id : <id_given_in_input>
955 id : <id_given_in_input>
953 result: {
956 result: {
954 "msg": "Created fork of `<reponame>` as `<forkname>`",
957 "msg": "Created fork of `<reponame>` as `<forkname>`",
955 "success": true,
958 "success": true,
956 "task": "<celery task id or None if done sync>"
959 "task": "<celery task id or None if done sync>"
957 }
960 }
958 error: null
961 error: null
959
962
960 """
963 """
961 if not has_superadmin_permission(apiuser):
964 if not has_superadmin_permission(apiuser):
962 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
965 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
963 raise JSONRPCForbidden()
966 raise JSONRPCForbidden()
964
967
965 repo = get_repo_or_error(repoid)
968 repo = get_repo_or_error(repoid)
966 repo_name = repo.repo_name
969 repo_name = repo.repo_name
967
970
968 (fork_name_cleaned,
971 (fork_name_cleaned,
969 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(
972 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(
970 fork_name)
973 fork_name)
971
974
972 if not has_superadmin_permission(apiuser):
975 if not has_superadmin_permission(apiuser):
973 # check if we have at least read permission for
976 # check if we have at least read permission for
974 # this repo that we fork !
977 # this repo that we fork !
975 _perms = (
978 _perms = (
976 'repository.admin', 'repository.write', 'repository.read')
979 'repository.admin', 'repository.write', 'repository.read')
977 has_repo_permissions(apiuser, repoid, repo, _perms)
980 has_repo_permissions(apiuser, repoid, repo, _perms)
978
981
979 if not isinstance(owner, Optional):
982 if not isinstance(owner, Optional):
980 # forbid setting owner for non super admins
983 # forbid setting owner for non super admins
981 raise JSONRPCError(
984 raise JSONRPCError(
982 'Only RhodeCode admin can specify `owner` param'
985 'Only RhodeCode admin can specify `owner` param'
983 )
986 )
984 # check if we have a create.repo permission if not maybe the parent
987 # check if we have a create.repo permission if not maybe the parent
985 # group permission
988 # group permission
986 if not HasPermissionAnyApi('hg.create.repository')(user=apiuser):
989 if not HasPermissionAnyApi('hg.create.repository')(user=apiuser):
987 if parent_group_name:
990 if parent_group_name:
988 repogroupid = parent_group_name
991 repogroupid = parent_group_name
989 repo_group = get_repo_group_or_error(parent_group_name)
992 repo_group = get_repo_group_or_error(parent_group_name)
990
993
991 _perms = ('group.admin',)
994 _perms = ('group.admin',)
992 if not HasRepoGroupPermissionAnyApi(*_perms)(
995 if not HasRepoGroupPermissionAnyApi(*_perms)(
993 user=apiuser, group_name=repo_group.group_name):
996 user=apiuser, group_name=repo_group.group_name):
994 raise JSONRPCError(
997 raise JSONRPCError(
995 'repository group `%s` does not exist' % (
998 'repository group `%s` does not exist' % (
996 repogroupid,))
999 repogroupid,))
997 else:
1000 else:
998 raise JSONRPCForbidden()
1001 raise JSONRPCForbidden()
999
1002
1000 _repo = RepoModel().get_by_repo_name(fork_name)
1003 _repo = RepoModel().get_by_repo_name(fork_name)
1001 if _repo:
1004 if _repo:
1002 type_ = 'fork' if _repo.fork else 'repo'
1005 type_ = 'fork' if _repo.fork else 'repo'
1003 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1006 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1004
1007
1005 if isinstance(owner, Optional):
1008 if isinstance(owner, Optional):
1006 owner = apiuser.user_id
1009 owner = apiuser.user_id
1007
1010
1008 owner = get_user_or_error(owner)
1011 owner = get_user_or_error(owner)
1009
1012
1010 try:
1013 try:
1011 # create structure of groups and return the last group
1014 # create structure of groups and return the last group
1012 repo_group = map_groups(fork_name)
1015 repo_group = map_groups(fork_name)
1013 form_data = {
1016 form_data = {
1014 'repo_name': fork_name_cleaned,
1017 'repo_name': fork_name_cleaned,
1015 'repo_name_full': fork_name,
1018 'repo_name_full': fork_name,
1016 'repo_group': repo_group.group_id if repo_group else None,
1019 'repo_group': repo_group.group_id if repo_group else None,
1017 'repo_type': repo.repo_type,
1020 'repo_type': repo.repo_type,
1018 'description': Optional.extract(description),
1021 'description': Optional.extract(description),
1019 'private': Optional.extract(private),
1022 'private': Optional.extract(private),
1020 'copy_permissions': Optional.extract(copy_permissions),
1023 'copy_permissions': Optional.extract(copy_permissions),
1021 'landing_rev': Optional.extract(landing_rev),
1024 'landing_rev': Optional.extract(landing_rev),
1022 'fork_parent_id': repo.repo_id,
1025 'fork_parent_id': repo.repo_id,
1023 }
1026 }
1024
1027
1025 task = RepoModel().create_fork(form_data, cur_user=owner)
1028 task = RepoModel().create_fork(form_data, cur_user=owner)
1026 # no commit, it's done in RepoModel, or async via celery
1029 # no commit, it's done in RepoModel, or async via celery
1027 from celery.result import BaseAsyncResult
1030 from celery.result import BaseAsyncResult
1028 task_id = None
1031 task_id = None
1029 if isinstance(task, BaseAsyncResult):
1032 if isinstance(task, BaseAsyncResult):
1030 task_id = task.task_id
1033 task_id = task.task_id
1031 return {
1034 return {
1032 'msg': 'Created fork of `%s` as `%s`' % (
1035 'msg': 'Created fork of `%s` as `%s`' % (
1033 repo.repo_name, fork_name),
1036 repo.repo_name, fork_name),
1034 'success': True, # cannot return the repo data here since fork
1037 'success': True, # cannot return the repo data here since fork
1035 # can be done async
1038 # can be done async
1036 'task': task_id
1039 'task': task_id
1037 }
1040 }
1038 except Exception:
1041 except Exception:
1039 log.exception("Exception occurred while trying to fork a repo")
1042 log.exception("Exception occurred while trying to fork a repo")
1040 raise JSONRPCError(
1043 raise JSONRPCError(
1041 'failed to fork repository `%s` as `%s`' % (
1044 'failed to fork repository `%s` as `%s`' % (
1042 repo_name, fork_name))
1045 repo_name, fork_name))
1043
1046
1044
1047
1045 @jsonrpc_method()
1048 @jsonrpc_method()
1046 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1049 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1047 """
1050 """
1048 Deletes a repository.
1051 Deletes a repository.
1049
1052
1050 * When the `forks` parameter is set it's possible to detach or delete
1053 * When the `forks` parameter is set it's possible to detach or delete
1051 forks of deleted repository.
1054 forks of deleted repository.
1052
1055
1053 This command can only be run using an |authtoken| with admin
1056 This command can only be run using an |authtoken| with admin
1054 permissions on the |repo|.
1057 permissions on the |repo|.
1055
1058
1056 :param apiuser: This is filled automatically from the |authtoken|.
1059 :param apiuser: This is filled automatically from the |authtoken|.
1057 :type apiuser: AuthUser
1060 :type apiuser: AuthUser
1058 :param repoid: Set the repository name or repository ID.
1061 :param repoid: Set the repository name or repository ID.
1059 :type repoid: str or int
1062 :type repoid: str or int
1060 :param forks: Set to `detach` or `delete` forks from the |repo|.
1063 :param forks: Set to `detach` or `delete` forks from the |repo|.
1061 :type forks: Optional(str)
1064 :type forks: Optional(str)
1062
1065
1063 Example error output:
1066 Example error output:
1064
1067
1065 .. code-block:: bash
1068 .. code-block:: bash
1066
1069
1067 id : <id_given_in_input>
1070 id : <id_given_in_input>
1068 result: {
1071 result: {
1069 "msg": "Deleted repository `<reponame>`",
1072 "msg": "Deleted repository `<reponame>`",
1070 "success": true
1073 "success": true
1071 }
1074 }
1072 error: null
1075 error: null
1073 """
1076 """
1074
1077
1075 repo = get_repo_or_error(repoid)
1078 repo = get_repo_or_error(repoid)
1076 if not has_superadmin_permission(apiuser):
1079 if not has_superadmin_permission(apiuser):
1077 _perms = ('repository.admin',)
1080 _perms = ('repository.admin',)
1078 has_repo_permissions(apiuser, repoid, repo, _perms)
1081 has_repo_permissions(apiuser, repoid, repo, _perms)
1079
1082
1080 try:
1083 try:
1081 handle_forks = Optional.extract(forks)
1084 handle_forks = Optional.extract(forks)
1082 _forks_msg = ''
1085 _forks_msg = ''
1083 _forks = [f for f in repo.forks]
1086 _forks = [f for f in repo.forks]
1084 if handle_forks == 'detach':
1087 if handle_forks == 'detach':
1085 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1088 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1086 elif handle_forks == 'delete':
1089 elif handle_forks == 'delete':
1087 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1090 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1088 elif _forks:
1091 elif _forks:
1089 raise JSONRPCError(
1092 raise JSONRPCError(
1090 'Cannot delete `%s` it still contains attached forks' %
1093 'Cannot delete `%s` it still contains attached forks' %
1091 (repo.repo_name,)
1094 (repo.repo_name,)
1092 )
1095 )
1093
1096
1094 RepoModel().delete(repo, forks=forks)
1097 RepoModel().delete(repo, forks=forks)
1095 Session().commit()
1098 Session().commit()
1096 return {
1099 return {
1097 'msg': 'Deleted repository `%s`%s' % (
1100 'msg': 'Deleted repository `%s`%s' % (
1098 repo.repo_name, _forks_msg),
1101 repo.repo_name, _forks_msg),
1099 'success': True
1102 'success': True
1100 }
1103 }
1101 except Exception:
1104 except Exception:
1102 log.exception("Exception occurred while trying to delete repo")
1105 log.exception("Exception occurred while trying to delete repo")
1103 raise JSONRPCError(
1106 raise JSONRPCError(
1104 'failed to delete repository `%s`' % (repo.repo_name,)
1107 'failed to delete repository `%s`' % (repo.repo_name,)
1105 )
1108 )
1106
1109
1107
1110
1108 #TODO: marcink, change name ?
1111 #TODO: marcink, change name ?
1109 @jsonrpc_method()
1112 @jsonrpc_method()
1110 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1113 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1111 """
1114 """
1112 Invalidates the cache for the specified repository.
1115 Invalidates the cache for the specified repository.
1113
1116
1114 This command can only be run using an |authtoken| with admin rights to
1117 This command can only be run using an |authtoken| with admin rights to
1115 the specified repository.
1118 the specified repository.
1116
1119
1117 This command takes the following options:
1120 This command takes the following options:
1118
1121
1119 :param apiuser: This is filled automatically from |authtoken|.
1122 :param apiuser: This is filled automatically from |authtoken|.
1120 :type apiuser: AuthUser
1123 :type apiuser: AuthUser
1121 :param repoid: Sets the repository name or repository ID.
1124 :param repoid: Sets the repository name or repository ID.
1122 :type repoid: str or int
1125 :type repoid: str or int
1123 :param delete_keys: This deletes the invalidated keys instead of
1126 :param delete_keys: This deletes the invalidated keys instead of
1124 just flagging them.
1127 just flagging them.
1125 :type delete_keys: Optional(``True`` | ``False``)
1128 :type delete_keys: Optional(``True`` | ``False``)
1126
1129
1127 Example output:
1130 Example output:
1128
1131
1129 .. code-block:: bash
1132 .. code-block:: bash
1130
1133
1131 id : <id_given_in_input>
1134 id : <id_given_in_input>
1132 result : {
1135 result : {
1133 'msg': Cache for repository `<repository name>` was invalidated,
1136 'msg': Cache for repository `<repository name>` was invalidated,
1134 'repository': <repository name>
1137 'repository': <repository name>
1135 }
1138 }
1136 error : null
1139 error : null
1137
1140
1138 Example error output:
1141 Example error output:
1139
1142
1140 .. code-block:: bash
1143 .. code-block:: bash
1141
1144
1142 id : <id_given_in_input>
1145 id : <id_given_in_input>
1143 result : null
1146 result : null
1144 error : {
1147 error : {
1145 'Error occurred during cache invalidation action'
1148 'Error occurred during cache invalidation action'
1146 }
1149 }
1147
1150
1148 """
1151 """
1149
1152
1150 repo = get_repo_or_error(repoid)
1153 repo = get_repo_or_error(repoid)
1151 if not has_superadmin_permission(apiuser):
1154 if not has_superadmin_permission(apiuser):
1152 _perms = ('repository.admin', 'repository.write',)
1155 _perms = ('repository.admin', 'repository.write',)
1153 has_repo_permissions(apiuser, repoid, repo, _perms)
1156 has_repo_permissions(apiuser, repoid, repo, _perms)
1154
1157
1155 delete = Optional.extract(delete_keys)
1158 delete = Optional.extract(delete_keys)
1156 try:
1159 try:
1157 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1160 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1158 return {
1161 return {
1159 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1162 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1160 'repository': repo.repo_name
1163 'repository': repo.repo_name
1161 }
1164 }
1162 except Exception:
1165 except Exception:
1163 log.exception(
1166 log.exception(
1164 "Exception occurred while trying to invalidate repo cache")
1167 "Exception occurred while trying to invalidate repo cache")
1165 raise JSONRPCError(
1168 raise JSONRPCError(
1166 'Error occurred during cache invalidation action'
1169 'Error occurred during cache invalidation action'
1167 )
1170 )
1168
1171
1169
1172
1170 #TODO: marcink, change name ?
1173 #TODO: marcink, change name ?
1171 @jsonrpc_method()
1174 @jsonrpc_method()
1172 def lock(request, apiuser, repoid, locked=Optional(None),
1175 def lock(request, apiuser, repoid, locked=Optional(None),
1173 userid=Optional(OAttr('apiuser'))):
1176 userid=Optional(OAttr('apiuser'))):
1174 """
1177 """
1175 Sets the lock state of the specified |repo| by the given user.
1178 Sets the lock state of the specified |repo| by the given user.
1176 From more information, see :ref:`repo-locking`.
1179 From more information, see :ref:`repo-locking`.
1177
1180
1178 * If the ``userid`` option is not set, the repository is locked to the
1181 * If the ``userid`` option is not set, the repository is locked to the
1179 user who called the method.
1182 user who called the method.
1180 * If the ``locked`` parameter is not set, the current lock state of the
1183 * If the ``locked`` parameter is not set, the current lock state of the
1181 repository is displayed.
1184 repository is displayed.
1182
1185
1183 This command can only be run using an |authtoken| with admin rights to
1186 This command can only be run using an |authtoken| with admin rights to
1184 the specified repository.
1187 the specified repository.
1185
1188
1186 This command takes the following options:
1189 This command takes the following options:
1187
1190
1188 :param apiuser: This is filled automatically from the |authtoken|.
1191 :param apiuser: This is filled automatically from the |authtoken|.
1189 :type apiuser: AuthUser
1192 :type apiuser: AuthUser
1190 :param repoid: Sets the repository name or repository ID.
1193 :param repoid: Sets the repository name or repository ID.
1191 :type repoid: str or int
1194 :type repoid: str or int
1192 :param locked: Sets the lock state.
1195 :param locked: Sets the lock state.
1193 :type locked: Optional(``True`` | ``False``)
1196 :type locked: Optional(``True`` | ``False``)
1194 :param userid: Set the repository lock to this user.
1197 :param userid: Set the repository lock to this user.
1195 :type userid: Optional(str or int)
1198 :type userid: Optional(str or int)
1196
1199
1197 Example error output:
1200 Example error output:
1198
1201
1199 .. code-block:: bash
1202 .. code-block:: bash
1200
1203
1201 id : <id_given_in_input>
1204 id : <id_given_in_input>
1202 result : {
1205 result : {
1203 'repo': '<reponame>',
1206 'repo': '<reponame>',
1204 'locked': <bool: lock state>,
1207 'locked': <bool: lock state>,
1205 'locked_since': <int: lock timestamp>,
1208 'locked_since': <int: lock timestamp>,
1206 'locked_by': <username of person who made the lock>,
1209 'locked_by': <username of person who made the lock>,
1207 'lock_reason': <str: reason for locking>,
1210 'lock_reason': <str: reason for locking>,
1208 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1211 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1209 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1212 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1210 or
1213 or
1211 'msg': 'Repo `<repository name>` not locked.'
1214 'msg': 'Repo `<repository name>` not locked.'
1212 or
1215 or
1213 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1216 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1214 }
1217 }
1215 error : null
1218 error : null
1216
1219
1217 Example error output:
1220 Example error output:
1218
1221
1219 .. code-block:: bash
1222 .. code-block:: bash
1220
1223
1221 id : <id_given_in_input>
1224 id : <id_given_in_input>
1222 result : null
1225 result : null
1223 error : {
1226 error : {
1224 'Error occurred locking repository `<reponame>`
1227 'Error occurred locking repository `<reponame>`
1225 }
1228 }
1226 """
1229 """
1227
1230
1228 repo = get_repo_or_error(repoid)
1231 repo = get_repo_or_error(repoid)
1229 if not has_superadmin_permission(apiuser):
1232 if not has_superadmin_permission(apiuser):
1230 # check if we have at least write permission for this repo !
1233 # check if we have at least write permission for this repo !
1231 _perms = ('repository.admin', 'repository.write',)
1234 _perms = ('repository.admin', 'repository.write',)
1232 has_repo_permissions(apiuser, repoid, repo, _perms)
1235 has_repo_permissions(apiuser, repoid, repo, _perms)
1233
1236
1234 # make sure normal user does not pass someone else userid,
1237 # make sure normal user does not pass someone else userid,
1235 # he is not allowed to do that
1238 # he is not allowed to do that
1236 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1239 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1237 raise JSONRPCError('userid is not the same as your user')
1240 raise JSONRPCError('userid is not the same as your user')
1238
1241
1239 if isinstance(userid, Optional):
1242 if isinstance(userid, Optional):
1240 userid = apiuser.user_id
1243 userid = apiuser.user_id
1241
1244
1242 user = get_user_or_error(userid)
1245 user = get_user_or_error(userid)
1243
1246
1244 if isinstance(locked, Optional):
1247 if isinstance(locked, Optional):
1245 lockobj = repo.locked
1248 lockobj = repo.locked
1246
1249
1247 if lockobj[0] is None:
1250 if lockobj[0] is None:
1248 _d = {
1251 _d = {
1249 'repo': repo.repo_name,
1252 'repo': repo.repo_name,
1250 'locked': False,
1253 'locked': False,
1251 'locked_since': None,
1254 'locked_since': None,
1252 'locked_by': None,
1255 'locked_by': None,
1253 'lock_reason': None,
1256 'lock_reason': None,
1254 'lock_state_changed': False,
1257 'lock_state_changed': False,
1255 'msg': 'Repo `%s` not locked.' % repo.repo_name
1258 'msg': 'Repo `%s` not locked.' % repo.repo_name
1256 }
1259 }
1257 return _d
1260 return _d
1258 else:
1261 else:
1259 _user_id, _time, _reason = lockobj
1262 _user_id, _time, _reason = lockobj
1260 lock_user = get_user_or_error(userid)
1263 lock_user = get_user_or_error(userid)
1261 _d = {
1264 _d = {
1262 'repo': repo.repo_name,
1265 'repo': repo.repo_name,
1263 'locked': True,
1266 'locked': True,
1264 'locked_since': _time,
1267 'locked_since': _time,
1265 'locked_by': lock_user.username,
1268 'locked_by': lock_user.username,
1266 'lock_reason': _reason,
1269 'lock_reason': _reason,
1267 'lock_state_changed': False,
1270 'lock_state_changed': False,
1268 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1271 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1269 % (repo.repo_name, lock_user.username,
1272 % (repo.repo_name, lock_user.username,
1270 json.dumps(time_to_datetime(_time))))
1273 json.dumps(time_to_datetime(_time))))
1271 }
1274 }
1272 return _d
1275 return _d
1273
1276
1274 # force locked state through a flag
1277 # force locked state through a flag
1275 else:
1278 else:
1276 locked = str2bool(locked)
1279 locked = str2bool(locked)
1277 lock_reason = Repository.LOCK_API
1280 lock_reason = Repository.LOCK_API
1278 try:
1281 try:
1279 if locked:
1282 if locked:
1280 lock_time = time.time()
1283 lock_time = time.time()
1281 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1284 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1282 else:
1285 else:
1283 lock_time = None
1286 lock_time = None
1284 Repository.unlock(repo)
1287 Repository.unlock(repo)
1285 _d = {
1288 _d = {
1286 'repo': repo.repo_name,
1289 'repo': repo.repo_name,
1287 'locked': locked,
1290 'locked': locked,
1288 'locked_since': lock_time,
1291 'locked_since': lock_time,
1289 'locked_by': user.username,
1292 'locked_by': user.username,
1290 'lock_reason': lock_reason,
1293 'lock_reason': lock_reason,
1291 'lock_state_changed': True,
1294 'lock_state_changed': True,
1292 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1295 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1293 % (user.username, repo.repo_name, locked))
1296 % (user.username, repo.repo_name, locked))
1294 }
1297 }
1295 return _d
1298 return _d
1296 except Exception:
1299 except Exception:
1297 log.exception(
1300 log.exception(
1298 "Exception occurred while trying to lock repository")
1301 "Exception occurred while trying to lock repository")
1299 raise JSONRPCError(
1302 raise JSONRPCError(
1300 'Error occurred locking repository `%s`' % repo.repo_name
1303 'Error occurred locking repository `%s`' % repo.repo_name
1301 )
1304 )
1302
1305
1303
1306
1304 @jsonrpc_method()
1307 @jsonrpc_method()
1305 def comment_commit(
1308 def comment_commit(
1306 request, apiuser, repoid, commit_id, message,
1309 request, apiuser, repoid, commit_id, message,
1307 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1310 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1308 """
1311 """
1309 Set a commit comment, and optionally change the status of the commit.
1312 Set a commit comment, and optionally change the status of the commit.
1310 This command can be executed only using api_key belonging to user
1313 This command can be executed only using api_key belonging to user
1311 with admin rights, or repository administrator.
1314 with admin rights, or repository administrator.
1312
1315
1313 :param apiuser: This is filled automatically from the |authtoken|.
1316 :param apiuser: This is filled automatically from the |authtoken|.
1314 :type apiuser: AuthUser
1317 :type apiuser: AuthUser
1315 :param repoid: Set the repository name or repository ID.
1318 :param repoid: Set the repository name or repository ID.
1316 :type repoid: str or int
1319 :type repoid: str or int
1317 :param commit_id: Specify the commit_id for which to set a comment.
1320 :param commit_id: Specify the commit_id for which to set a comment.
1318 :type commit_id: str
1321 :type commit_id: str
1319 :param message: The comment text.
1322 :param message: The comment text.
1320 :type message: str
1323 :type message: str
1321 :param userid: Set the user name of the comment creator.
1324 :param userid: Set the user name of the comment creator.
1322 :type userid: Optional(str or int)
1325 :type userid: Optional(str or int)
1323 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1326 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1324 'under_review'
1327 'under_review'
1325 :type status: str
1328 :type status: str
1326
1329
1327 Example error output:
1330 Example error output:
1328
1331
1329 .. code-block:: json
1332 .. code-block:: json
1330
1333
1331 {
1334 {
1332 "id" : <id_given_in_input>,
1335 "id" : <id_given_in_input>,
1333 "result" : {
1336 "result" : {
1334 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1337 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1335 "status_change": null or <status>,
1338 "status_change": null or <status>,
1336 "success": true
1339 "success": true
1337 },
1340 },
1338 "error" : null
1341 "error" : null
1339 }
1342 }
1340
1343
1341 """
1344 """
1342 repo = get_repo_or_error(repoid)
1345 repo = get_repo_or_error(repoid)
1343 if not has_superadmin_permission(apiuser):
1346 if not has_superadmin_permission(apiuser):
1344 _perms = ('repository.admin',)
1347 _perms = ('repository.admin',)
1345 has_repo_permissions(apiuser, repoid, repo, _perms)
1348 has_repo_permissions(apiuser, repoid, repo, _perms)
1346
1349
1347 if isinstance(userid, Optional):
1350 if isinstance(userid, Optional):
1348 userid = apiuser.user_id
1351 userid = apiuser.user_id
1349
1352
1350 user = get_user_or_error(userid)
1353 user = get_user_or_error(userid)
1351 status = Optional.extract(status)
1354 status = Optional.extract(status)
1352
1355
1353 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1356 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1354 if status and status not in allowed_statuses:
1357 if status and status not in allowed_statuses:
1355 raise JSONRPCError('Bad status, must be on '
1358 raise JSONRPCError('Bad status, must be on '
1356 'of %s got %s' % (allowed_statuses, status,))
1359 'of %s got %s' % (allowed_statuses, status,))
1357
1360
1358 try:
1361 try:
1359 rc_config = SettingsModel().get_all_settings()
1362 rc_config = SettingsModel().get_all_settings()
1360 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1363 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1361
1364
1362 comm = ChangesetCommentsModel().create(
1365 comm = ChangesetCommentsModel().create(
1363 message, repo, user, revision=commit_id, status_change=status,
1366 message, repo, user, revision=commit_id, status_change=status,
1364 renderer=renderer)
1367 renderer=renderer)
1365 if status:
1368 if status:
1366 # also do a status change
1369 # also do a status change
1367 try:
1370 try:
1368 ChangesetStatusModel().set_status(
1371 ChangesetStatusModel().set_status(
1369 repo, status, user, comm, revision=commit_id,
1372 repo, status, user, comm, revision=commit_id,
1370 dont_allow_on_closed_pull_request=True
1373 dont_allow_on_closed_pull_request=True
1371 )
1374 )
1372 except StatusChangeOnClosedPullRequestError:
1375 except StatusChangeOnClosedPullRequestError:
1373 log.exception(
1376 log.exception(
1374 "Exception occurred while trying to change repo commit status")
1377 "Exception occurred while trying to change repo commit status")
1375 msg = ('Changing status on a changeset associated with '
1378 msg = ('Changing status on a changeset associated with '
1376 'a closed pull request is not allowed')
1379 'a closed pull request is not allowed')
1377 raise JSONRPCError(msg)
1380 raise JSONRPCError(msg)
1378
1381
1379 Session().commit()
1382 Session().commit()
1380 return {
1383 return {
1381 'msg': (
1384 'msg': (
1382 'Commented on commit `%s` for repository `%s`' % (
1385 'Commented on commit `%s` for repository `%s`' % (
1383 comm.revision, repo.repo_name)),
1386 comm.revision, repo.repo_name)),
1384 'status_change': status,
1387 'status_change': status,
1385 'success': True,
1388 'success': True,
1386 }
1389 }
1387 except JSONRPCError:
1390 except JSONRPCError:
1388 # catch any inside errors, and re-raise them to prevent from
1391 # catch any inside errors, and re-raise them to prevent from
1389 # below global catch to silence them
1392 # below global catch to silence them
1390 raise
1393 raise
1391 except Exception:
1394 except Exception:
1392 log.exception("Exception occurred while trying to comment on commit")
1395 log.exception("Exception occurred while trying to comment on commit")
1393 raise JSONRPCError(
1396 raise JSONRPCError(
1394 'failed to set comment on repository `%s`' % (repo.repo_name,)
1397 'failed to set comment on repository `%s`' % (repo.repo_name,)
1395 )
1398 )
1396
1399
1397
1400
1398 @jsonrpc_method()
1401 @jsonrpc_method()
1399 def grant_user_permission(request, apiuser, repoid, userid, perm):
1402 def grant_user_permission(request, apiuser, repoid, userid, perm):
1400 """
1403 """
1401 Grant permissions for the specified user on the given repository,
1404 Grant permissions for the specified user on the given repository,
1402 or update existing permissions if found.
1405 or update existing permissions if found.
1403
1406
1404 This command can only be run using an |authtoken| with admin
1407 This command can only be run using an |authtoken| with admin
1405 permissions on the |repo|.
1408 permissions on the |repo|.
1406
1409
1407 :param apiuser: This is filled automatically from the |authtoken|.
1410 :param apiuser: This is filled automatically from the |authtoken|.
1408 :type apiuser: AuthUser
1411 :type apiuser: AuthUser
1409 :param repoid: Set the repository name or repository ID.
1412 :param repoid: Set the repository name or repository ID.
1410 :type repoid: str or int
1413 :type repoid: str or int
1411 :param userid: Set the user name.
1414 :param userid: Set the user name.
1412 :type userid: str
1415 :type userid: str
1413 :param perm: Set the user permissions, using the following format
1416 :param perm: Set the user permissions, using the following format
1414 ``(repository.(none|read|write|admin))``
1417 ``(repository.(none|read|write|admin))``
1415 :type perm: str
1418 :type perm: str
1416
1419
1417 Example output:
1420 Example output:
1418
1421
1419 .. code-block:: bash
1422 .. code-block:: bash
1420
1423
1421 id : <id_given_in_input>
1424 id : <id_given_in_input>
1422 result: {
1425 result: {
1423 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1426 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1424 "success": true
1427 "success": true
1425 }
1428 }
1426 error: null
1429 error: null
1427 """
1430 """
1428
1431
1429 repo = get_repo_or_error(repoid)
1432 repo = get_repo_or_error(repoid)
1430 user = get_user_or_error(userid)
1433 user = get_user_or_error(userid)
1431 perm = get_perm_or_error(perm)
1434 perm = get_perm_or_error(perm)
1432 if not has_superadmin_permission(apiuser):
1435 if not has_superadmin_permission(apiuser):
1433 _perms = ('repository.admin',)
1436 _perms = ('repository.admin',)
1434 has_repo_permissions(apiuser, repoid, repo, _perms)
1437 has_repo_permissions(apiuser, repoid, repo, _perms)
1435
1438
1436 try:
1439 try:
1437
1440
1438 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1441 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1439
1442
1440 Session().commit()
1443 Session().commit()
1441 return {
1444 return {
1442 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1445 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1443 perm.permission_name, user.username, repo.repo_name
1446 perm.permission_name, user.username, repo.repo_name
1444 ),
1447 ),
1445 'success': True
1448 'success': True
1446 }
1449 }
1447 except Exception:
1450 except Exception:
1448 log.exception(
1451 log.exception(
1449 "Exception occurred while trying edit permissions for repo")
1452 "Exception occurred while trying edit permissions for repo")
1450 raise JSONRPCError(
1453 raise JSONRPCError(
1451 'failed to edit permission for user: `%s` in repo: `%s`' % (
1454 'failed to edit permission for user: `%s` in repo: `%s`' % (
1452 userid, repoid
1455 userid, repoid
1453 )
1456 )
1454 )
1457 )
1455
1458
1456
1459
1457 @jsonrpc_method()
1460 @jsonrpc_method()
1458 def revoke_user_permission(request, apiuser, repoid, userid):
1461 def revoke_user_permission(request, apiuser, repoid, userid):
1459 """
1462 """
1460 Revoke permission for a user on the specified repository.
1463 Revoke permission for a user on the specified repository.
1461
1464
1462 This command can only be run using an |authtoken| with admin
1465 This command can only be run using an |authtoken| with admin
1463 permissions on the |repo|.
1466 permissions on the |repo|.
1464
1467
1465 :param apiuser: This is filled automatically from the |authtoken|.
1468 :param apiuser: This is filled automatically from the |authtoken|.
1466 :type apiuser: AuthUser
1469 :type apiuser: AuthUser
1467 :param repoid: Set the repository name or repository ID.
1470 :param repoid: Set the repository name or repository ID.
1468 :type repoid: str or int
1471 :type repoid: str or int
1469 :param userid: Set the user name of revoked user.
1472 :param userid: Set the user name of revoked user.
1470 :type userid: str or int
1473 :type userid: str or int
1471
1474
1472 Example error output:
1475 Example error output:
1473
1476
1474 .. code-block:: bash
1477 .. code-block:: bash
1475
1478
1476 id : <id_given_in_input>
1479 id : <id_given_in_input>
1477 result: {
1480 result: {
1478 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1481 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1479 "success": true
1482 "success": true
1480 }
1483 }
1481 error: null
1484 error: null
1482 """
1485 """
1483
1486
1484 repo = get_repo_or_error(repoid)
1487 repo = get_repo_or_error(repoid)
1485 user = get_user_or_error(userid)
1488 user = get_user_or_error(userid)
1486 if not has_superadmin_permission(apiuser):
1489 if not has_superadmin_permission(apiuser):
1487 _perms = ('repository.admin',)
1490 _perms = ('repository.admin',)
1488 has_repo_permissions(apiuser, repoid, repo, _perms)
1491 has_repo_permissions(apiuser, repoid, repo, _perms)
1489
1492
1490 try:
1493 try:
1491 RepoModel().revoke_user_permission(repo=repo, user=user)
1494 RepoModel().revoke_user_permission(repo=repo, user=user)
1492 Session().commit()
1495 Session().commit()
1493 return {
1496 return {
1494 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1497 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1495 user.username, repo.repo_name
1498 user.username, repo.repo_name
1496 ),
1499 ),
1497 'success': True
1500 'success': True
1498 }
1501 }
1499 except Exception:
1502 except Exception:
1500 log.exception(
1503 log.exception(
1501 "Exception occurred while trying revoke permissions to repo")
1504 "Exception occurred while trying revoke permissions to repo")
1502 raise JSONRPCError(
1505 raise JSONRPCError(
1503 'failed to edit permission for user: `%s` in repo: `%s`' % (
1506 'failed to edit permission for user: `%s` in repo: `%s`' % (
1504 userid, repoid
1507 userid, repoid
1505 )
1508 )
1506 )
1509 )
1507
1510
1508
1511
1509 @jsonrpc_method()
1512 @jsonrpc_method()
1510 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1513 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1511 """
1514 """
1512 Grant permission for a user group on the specified repository,
1515 Grant permission for a user group on the specified repository,
1513 or update existing permissions.
1516 or update existing permissions.
1514
1517
1515 This command can only be run using an |authtoken| with admin
1518 This command can only be run using an |authtoken| with admin
1516 permissions on the |repo|.
1519 permissions on the |repo|.
1517
1520
1518 :param apiuser: This is filled automatically from the |authtoken|.
1521 :param apiuser: This is filled automatically from the |authtoken|.
1519 :type apiuser: AuthUser
1522 :type apiuser: AuthUser
1520 :param repoid: Set the repository name or repository ID.
1523 :param repoid: Set the repository name or repository ID.
1521 :type repoid: str or int
1524 :type repoid: str or int
1522 :param usergroupid: Specify the ID of the user group.
1525 :param usergroupid: Specify the ID of the user group.
1523 :type usergroupid: str or int
1526 :type usergroupid: str or int
1524 :param perm: Set the user group permissions using the following
1527 :param perm: Set the user group permissions using the following
1525 format: (repository.(none|read|write|admin))
1528 format: (repository.(none|read|write|admin))
1526 :type perm: str
1529 :type perm: str
1527
1530
1528 Example output:
1531 Example output:
1529
1532
1530 .. code-block:: bash
1533 .. code-block:: bash
1531
1534
1532 id : <id_given_in_input>
1535 id : <id_given_in_input>
1533 result : {
1536 result : {
1534 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1537 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1535 "success": true
1538 "success": true
1536
1539
1537 }
1540 }
1538 error : null
1541 error : null
1539
1542
1540 Example error output:
1543 Example error output:
1541
1544
1542 .. code-block:: bash
1545 .. code-block:: bash
1543
1546
1544 id : <id_given_in_input>
1547 id : <id_given_in_input>
1545 result : null
1548 result : null
1546 error : {
1549 error : {
1547 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1550 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1548 }
1551 }
1549
1552
1550 """
1553 """
1551
1554
1552 repo = get_repo_or_error(repoid)
1555 repo = get_repo_or_error(repoid)
1553 perm = get_perm_or_error(perm)
1556 perm = get_perm_or_error(perm)
1554 if not has_superadmin_permission(apiuser):
1557 if not has_superadmin_permission(apiuser):
1555 _perms = ('repository.admin',)
1558 _perms = ('repository.admin',)
1556 has_repo_permissions(apiuser, repoid, repo, _perms)
1559 has_repo_permissions(apiuser, repoid, repo, _perms)
1557
1560
1558 user_group = get_user_group_or_error(usergroupid)
1561 user_group = get_user_group_or_error(usergroupid)
1559 if not has_superadmin_permission(apiuser):
1562 if not has_superadmin_permission(apiuser):
1560 # check if we have at least read permission for this user group !
1563 # check if we have at least read permission for this user group !
1561 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1564 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1562 if not HasUserGroupPermissionAnyApi(*_perms)(
1565 if not HasUserGroupPermissionAnyApi(*_perms)(
1563 user=apiuser, user_group_name=user_group.users_group_name):
1566 user=apiuser, user_group_name=user_group.users_group_name):
1564 raise JSONRPCError(
1567 raise JSONRPCError(
1565 'user group `%s` does not exist' % (usergroupid,))
1568 'user group `%s` does not exist' % (usergroupid,))
1566
1569
1567 try:
1570 try:
1568 RepoModel().grant_user_group_permission(
1571 RepoModel().grant_user_group_permission(
1569 repo=repo, group_name=user_group, perm=perm)
1572 repo=repo, group_name=user_group, perm=perm)
1570
1573
1571 Session().commit()
1574 Session().commit()
1572 return {
1575 return {
1573 'msg': 'Granted perm: `%s` for user group: `%s` in '
1576 'msg': 'Granted perm: `%s` for user group: `%s` in '
1574 'repo: `%s`' % (
1577 'repo: `%s`' % (
1575 perm.permission_name, user_group.users_group_name,
1578 perm.permission_name, user_group.users_group_name,
1576 repo.repo_name
1579 repo.repo_name
1577 ),
1580 ),
1578 'success': True
1581 'success': True
1579 }
1582 }
1580 except Exception:
1583 except Exception:
1581 log.exception(
1584 log.exception(
1582 "Exception occurred while trying change permission on repo")
1585 "Exception occurred while trying change permission on repo")
1583 raise JSONRPCError(
1586 raise JSONRPCError(
1584 'failed to edit permission for user group: `%s` in '
1587 'failed to edit permission for user group: `%s` in '
1585 'repo: `%s`' % (
1588 'repo: `%s`' % (
1586 usergroupid, repo.repo_name
1589 usergroupid, repo.repo_name
1587 )
1590 )
1588 )
1591 )
1589
1592
1590
1593
1591 @jsonrpc_method()
1594 @jsonrpc_method()
1592 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1595 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1593 """
1596 """
1594 Revoke the permissions of a user group on a given repository.
1597 Revoke the permissions of a user group on a given repository.
1595
1598
1596 This command can only be run using an |authtoken| with admin
1599 This command can only be run using an |authtoken| with admin
1597 permissions on the |repo|.
1600 permissions on the |repo|.
1598
1601
1599 :param apiuser: This is filled automatically from the |authtoken|.
1602 :param apiuser: This is filled automatically from the |authtoken|.
1600 :type apiuser: AuthUser
1603 :type apiuser: AuthUser
1601 :param repoid: Set the repository name or repository ID.
1604 :param repoid: Set the repository name or repository ID.
1602 :type repoid: str or int
1605 :type repoid: str or int
1603 :param usergroupid: Specify the user group ID.
1606 :param usergroupid: Specify the user group ID.
1604 :type usergroupid: str or int
1607 :type usergroupid: str or int
1605
1608
1606 Example output:
1609 Example output:
1607
1610
1608 .. code-block:: bash
1611 .. code-block:: bash
1609
1612
1610 id : <id_given_in_input>
1613 id : <id_given_in_input>
1611 result: {
1614 result: {
1612 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1615 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1613 "success": true
1616 "success": true
1614 }
1617 }
1615 error: null
1618 error: null
1616 """
1619 """
1617
1620
1618 repo = get_repo_or_error(repoid)
1621 repo = get_repo_or_error(repoid)
1619 if not has_superadmin_permission(apiuser):
1622 if not has_superadmin_permission(apiuser):
1620 _perms = ('repository.admin',)
1623 _perms = ('repository.admin',)
1621 has_repo_permissions(apiuser, repoid, repo, _perms)
1624 has_repo_permissions(apiuser, repoid, repo, _perms)
1622
1625
1623 user_group = get_user_group_or_error(usergroupid)
1626 user_group = get_user_group_or_error(usergroupid)
1624 if not has_superadmin_permission(apiuser):
1627 if not has_superadmin_permission(apiuser):
1625 # check if we have at least read permission for this user group !
1628 # check if we have at least read permission for this user group !
1626 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1629 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1627 if not HasUserGroupPermissionAnyApi(*_perms)(
1630 if not HasUserGroupPermissionAnyApi(*_perms)(
1628 user=apiuser, user_group_name=user_group.users_group_name):
1631 user=apiuser, user_group_name=user_group.users_group_name):
1629 raise JSONRPCError(
1632 raise JSONRPCError(
1630 'user group `%s` does not exist' % (usergroupid,))
1633 'user group `%s` does not exist' % (usergroupid,))
1631
1634
1632 try:
1635 try:
1633 RepoModel().revoke_user_group_permission(
1636 RepoModel().revoke_user_group_permission(
1634 repo=repo, group_name=user_group)
1637 repo=repo, group_name=user_group)
1635
1638
1636 Session().commit()
1639 Session().commit()
1637 return {
1640 return {
1638 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1641 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1639 user_group.users_group_name, repo.repo_name
1642 user_group.users_group_name, repo.repo_name
1640 ),
1643 ),
1641 'success': True
1644 'success': True
1642 }
1645 }
1643 except Exception:
1646 except Exception:
1644 log.exception("Exception occurred while trying revoke "
1647 log.exception("Exception occurred while trying revoke "
1645 "user group permission on repo")
1648 "user group permission on repo")
1646 raise JSONRPCError(
1649 raise JSONRPCError(
1647 'failed to edit permission for user group: `%s` in '
1650 'failed to edit permission for user group: `%s` in '
1648 'repo: `%s`' % (
1651 'repo: `%s`' % (
1649 user_group.users_group_name, repo.repo_name
1652 user_group.users_group_name, repo.repo_name
1650 )
1653 )
1651 )
1654 )
1652
1655
1653
1656
1654 @jsonrpc_method()
1657 @jsonrpc_method()
1655 def pull(request, apiuser, repoid):
1658 def pull(request, apiuser, repoid):
1656 """
1659 """
1657 Triggers a pull on the given repository from a remote location. You
1660 Triggers a pull on the given repository from a remote location. You
1658 can use this to keep remote repositories up-to-date.
1661 can use this to keep remote repositories up-to-date.
1659
1662
1660 This command can only be run using an |authtoken| with admin
1663 This command can only be run using an |authtoken| with admin
1661 rights to the specified repository. For more information,
1664 rights to the specified repository. For more information,
1662 see :ref:`config-token-ref`.
1665 see :ref:`config-token-ref`.
1663
1666
1664 This command takes the following options:
1667 This command takes the following options:
1665
1668
1666 :param apiuser: This is filled automatically from the |authtoken|.
1669 :param apiuser: This is filled automatically from the |authtoken|.
1667 :type apiuser: AuthUser
1670 :type apiuser: AuthUser
1668 :param repoid: The repository name or repository ID.
1671 :param repoid: The repository name or repository ID.
1669 :type repoid: str or int
1672 :type repoid: str or int
1670
1673
1671 Example output:
1674 Example output:
1672
1675
1673 .. code-block:: bash
1676 .. code-block:: bash
1674
1677
1675 id : <id_given_in_input>
1678 id : <id_given_in_input>
1676 result : {
1679 result : {
1677 "msg": "Pulled from `<repository name>`"
1680 "msg": "Pulled from `<repository name>`"
1678 "repository": "<repository name>"
1681 "repository": "<repository name>"
1679 }
1682 }
1680 error : null
1683 error : null
1681
1684
1682 Example error output:
1685 Example error output:
1683
1686
1684 .. code-block:: bash
1687 .. code-block:: bash
1685
1688
1686 id : <id_given_in_input>
1689 id : <id_given_in_input>
1687 result : null
1690 result : null
1688 error : {
1691 error : {
1689 "Unable to pull changes from `<reponame>`"
1692 "Unable to pull changes from `<reponame>`"
1690 }
1693 }
1691
1694
1692 """
1695 """
1693
1696
1694 repo = get_repo_or_error(repoid)
1697 repo = get_repo_or_error(repoid)
1695 if not has_superadmin_permission(apiuser):
1698 if not has_superadmin_permission(apiuser):
1696 _perms = ('repository.admin',)
1699 _perms = ('repository.admin',)
1697 has_repo_permissions(apiuser, repoid, repo, _perms)
1700 has_repo_permissions(apiuser, repoid, repo, _perms)
1698
1701
1699 try:
1702 try:
1700 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1703 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1701 return {
1704 return {
1702 'msg': 'Pulled from `%s`' % repo.repo_name,
1705 'msg': 'Pulled from `%s`' % repo.repo_name,
1703 'repository': repo.repo_name
1706 'repository': repo.repo_name
1704 }
1707 }
1705 except Exception:
1708 except Exception:
1706 log.exception("Exception occurred while trying to "
1709 log.exception("Exception occurred while trying to "
1707 "pull changes from remote location")
1710 "pull changes from remote location")
1708 raise JSONRPCError(
1711 raise JSONRPCError(
1709 'Unable to pull changes from `%s`' % repo.repo_name
1712 'Unable to pull changes from `%s`' % repo.repo_name
1710 )
1713 )
1711
1714
1712
1715
1713 @jsonrpc_method()
1716 @jsonrpc_method()
1714 def strip(request, apiuser, repoid, revision, branch):
1717 def strip(request, apiuser, repoid, revision, branch):
1715 """
1718 """
1716 Strips the given revision from the specified repository.
1719 Strips the given revision from the specified repository.
1717
1720
1718 * This will remove the revision and all of its decendants.
1721 * This will remove the revision and all of its decendants.
1719
1722
1720 This command can only be run using an |authtoken| with admin rights to
1723 This command can only be run using an |authtoken| with admin rights to
1721 the specified repository.
1724 the specified repository.
1722
1725
1723 This command takes the following options:
1726 This command takes the following options:
1724
1727
1725 :param apiuser: This is filled automatically from the |authtoken|.
1728 :param apiuser: This is filled automatically from the |authtoken|.
1726 :type apiuser: AuthUser
1729 :type apiuser: AuthUser
1727 :param repoid: The repository name or repository ID.
1730 :param repoid: The repository name or repository ID.
1728 :type repoid: str or int
1731 :type repoid: str or int
1729 :param revision: The revision you wish to strip.
1732 :param revision: The revision you wish to strip.
1730 :type revision: str
1733 :type revision: str
1731 :param branch: The branch from which to strip the revision.
1734 :param branch: The branch from which to strip the revision.
1732 :type branch: str
1735 :type branch: str
1733
1736
1734 Example output:
1737 Example output:
1735
1738
1736 .. code-block:: bash
1739 .. code-block:: bash
1737
1740
1738 id : <id_given_in_input>
1741 id : <id_given_in_input>
1739 result : {
1742 result : {
1740 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1743 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1741 "repository": "<repository name>"
1744 "repository": "<repository name>"
1742 }
1745 }
1743 error : null
1746 error : null
1744
1747
1745 Example error output:
1748 Example error output:
1746
1749
1747 .. code-block:: bash
1750 .. code-block:: bash
1748
1751
1749 id : <id_given_in_input>
1752 id : <id_given_in_input>
1750 result : null
1753 result : null
1751 error : {
1754 error : {
1752 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1755 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1753 }
1756 }
1754
1757
1755 """
1758 """
1756
1759
1757 repo = get_repo_or_error(repoid)
1760 repo = get_repo_or_error(repoid)
1758 if not has_superadmin_permission(apiuser):
1761 if not has_superadmin_permission(apiuser):
1759 _perms = ('repository.admin',)
1762 _perms = ('repository.admin',)
1760 has_repo_permissions(apiuser, repoid, repo, _perms)
1763 has_repo_permissions(apiuser, repoid, repo, _perms)
1761
1764
1762 try:
1765 try:
1763 ScmModel().strip(repo, revision, branch)
1766 ScmModel().strip(repo, revision, branch)
1764 return {
1767 return {
1765 'msg': 'Stripped commit %s from repo `%s`' % (
1768 'msg': 'Stripped commit %s from repo `%s`' % (
1766 revision, repo.repo_name),
1769 revision, repo.repo_name),
1767 'repository': repo.repo_name
1770 'repository': repo.repo_name
1768 }
1771 }
1769 except Exception:
1772 except Exception:
1770 log.exception("Exception while trying to strip")
1773 log.exception("Exception while trying to strip")
1771 raise JSONRPCError(
1774 raise JSONRPCError(
1772 'Unable to strip commit %s from repo `%s`' % (
1775 'Unable to strip commit %s from repo `%s`' % (
1773 revision, repo.repo_name)
1776 revision, repo.repo_name)
1774 )
1777 )
@@ -1,85 +1,122 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import importlib
22
23
23 from pkg_resources import iter_entry_points
24 from pkg_resources import iter_entry_points
24 from pyramid.authentication import SessionAuthenticationPolicy
25 from pyramid.authentication import SessionAuthenticationPolicy
25
26
26 from rhodecode.authentication.registry import AuthenticationPluginRegistry
27 from rhodecode.authentication.registry import AuthenticationPluginRegistry
27 from rhodecode.authentication.routes import root_factory
28 from rhodecode.authentication.routes import root_factory
28 from rhodecode.authentication.routes import AuthnRootResource
29 from rhodecode.authentication.routes import AuthnRootResource
29 from rhodecode.config.routing import ADMIN_PREFIX
30 from rhodecode.config.routing import ADMIN_PREFIX
31 from rhodecode.model.settings import SettingsModel
32
30
33
31 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
32
35
36 # Plugin ID prefixes to distinct between normal and legacy plugins.
37 plugin_prefix = 'egg:'
38 legacy_plugin_prefix = 'py:'
39
33
40
34 # TODO: Currently this is only used to discover the authentication plugins.
41 # TODO: Currently this is only used to discover the authentication plugins.
35 # Later on this may be used in a generic way to look up and include all kinds
42 # Later on this may be used in a generic way to look up and include all kinds
36 # of supported enterprise plugins. Therefore this has to be moved and
43 # of supported enterprise plugins. Therefore this has to be moved and
37 # refactored to a real 'plugin look up' machinery.
44 # refactored to a real 'plugin look up' machinery.
38 # TODO: When refactoring this think about splitting it up into distinct
45 # TODO: When refactoring this think about splitting it up into distinct
39 # discover, load and include phases.
46 # discover, load and include phases.
40 def _discover_plugins(config, entry_point='enterprise.plugins1'):
47 def _discover_plugins(config, entry_point='enterprise.plugins1'):
41 _discovered_plugins = {}
42
43 for ep in iter_entry_points(entry_point):
48 for ep in iter_entry_points(entry_point):
44 plugin_id = 'egg:{}#{}'.format(ep.dist.project_name, ep.name)
49 plugin_id = '{}{}#{}'.format(
50 plugin_prefix, ep.dist.project_name, ep.name)
45 log.debug('Plugin discovered: "%s"', plugin_id)
51 log.debug('Plugin discovered: "%s"', plugin_id)
46 module = ep.load()
52 try:
47 plugin = module(plugin_id=plugin_id)
53 module = ep.load()
48 config.include(plugin.includeme)
54 plugin = module(plugin_id=plugin_id)
55 config.include(plugin.includeme)
56 except Exception as e:
57 log.exception(
58 'Exception while loading authentication plugin '
59 '"{}": {}'.format(plugin_id, e.message))
60
61
62 def _import_legacy_plugin(plugin_id):
63 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
64 module = importlib.import_module(module_name)
65 return module.plugin_factory(plugin_id=plugin_id)
66
49
67
50 return _discovered_plugins
68 def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
69 """
70 Function that imports the legacy plugins stored in the 'auth_plugins'
71 setting in database which are using the specified prefix. Normally 'py:' is
72 used for the legacy plugins.
73 """
74 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
75 enabled_plugins = auth_plugins.app_settings_value
76 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
77
78 for plugin_id in legacy_plugins:
79 log.debug('Legacy plugin discovered: "%s"', plugin_id)
80 try:
81 plugin = _import_legacy_plugin(plugin_id)
82 config.include(plugin.includeme)
83 except Exception as e:
84 log.exception(
85 'Exception while loading legacy authentication plugin '
86 '"{}": {}'.format(plugin_id, e.message))
51
87
52
88
53 def includeme(config):
89 def includeme(config):
54 # Set authentication policy.
90 # Set authentication policy.
55 authn_policy = SessionAuthenticationPolicy()
91 authn_policy = SessionAuthenticationPolicy()
56 config.set_authentication_policy(authn_policy)
92 config.set_authentication_policy(authn_policy)
57
93
58 # Create authentication plugin registry and add it to the pyramid registry.
94 # Create authentication plugin registry and add it to the pyramid registry.
59 authn_registry = AuthenticationPluginRegistry()
95 authn_registry = AuthenticationPluginRegistry(config.get_settings())
60 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
96 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
61 config.registry.registerUtility(authn_registry)
97 config.registry.registerUtility(authn_registry)
62
98
63 # Create authentication traversal root resource.
99 # Create authentication traversal root resource.
64 authn_root_resource = root_factory()
100 authn_root_resource = root_factory()
65 config.add_directive('add_authn_resource',
101 config.add_directive('add_authn_resource',
66 authn_root_resource.add_authn_resource)
102 authn_root_resource.add_authn_resource)
67
103
68 # Add the authentication traversal route.
104 # Add the authentication traversal route.
69 config.add_route('auth_home',
105 config.add_route('auth_home',
70 ADMIN_PREFIX + '/auth*traverse',
106 ADMIN_PREFIX + '/auth*traverse',
71 factory=root_factory)
107 factory=root_factory)
72 # Add the authentication settings root views.
108 # Add the authentication settings root views.
73 config.add_view('rhodecode.authentication.views.AuthSettingsView',
109 config.add_view('rhodecode.authentication.views.AuthSettingsView',
74 attr='index',
110 attr='index',
75 request_method='GET',
111 request_method='GET',
76 route_name='auth_home',
112 route_name='auth_home',
77 context=AuthnRootResource)
113 context=AuthnRootResource)
78 config.add_view('rhodecode.authentication.views.AuthSettingsView',
114 config.add_view('rhodecode.authentication.views.AuthSettingsView',
79 attr='auth_settings',
115 attr='auth_settings',
80 request_method='POST',
116 request_method='POST',
81 route_name='auth_home',
117 route_name='auth_home',
82 context=AuthnRootResource)
118 context=AuthnRootResource)
83
119
84 # Auto discover authentication plugins and include their configuration.
120 # Auto discover authentication plugins and include their configuration.
85 _discover_plugins(config)
121 _discover_plugins(config)
122 _discover_legacy_plugins(config)
@@ -1,739 +1,609 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Authentication modules
22 Authentication modules
23 """
23 """
24
24
25 import logging
25 import logging
26 import time
26 import time
27 import traceback
27 import traceback
28 import warnings
28
29
29 from authomatic import Authomatic
30 from authomatic.adapters import WebObAdapter
31 from authomatic.providers import oauth2, oauth1
32 from pylons import url
33 from pylons.controllers.util import Response
34 from pylons.i18n.translation import _
35 from pyramid.threadlocal import get_current_registry
30 from pyramid.threadlocal import get_current_registry
36 from sqlalchemy.ext.hybrid import hybrid_property
31 from sqlalchemy.ext.hybrid import hybrid_property
37
32
38 import rhodecode.lib.helpers as h
39 from rhodecode.authentication.interface import IAuthnPluginRegistry
33 from rhodecode.authentication.interface import IAuthnPluginRegistry
40 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
41 from rhodecode.lib import caches
35 from rhodecode.lib import caches
42 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
36 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
43 from rhodecode.lib.utils2 import md5_safe, safe_int
37 from rhodecode.lib.utils2 import md5_safe, safe_int
44 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.utils2 import safe_str
45 from rhodecode.model.db import User, ExternalIdentity
39 from rhodecode.model.db import User
46 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
47 from rhodecode.model.settings import SettingsModel
41 from rhodecode.model.settings import SettingsModel
48 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
49 from rhodecode.model.user_group import UserGroupModel
43 from rhodecode.model.user_group import UserGroupModel
50
44
51
45
52 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
53
47
54 # auth types that authenticate() function can receive
48 # auth types that authenticate() function can receive
55 VCS_TYPE = 'vcs'
49 VCS_TYPE = 'vcs'
56 HTTP_TYPE = 'http'
50 HTTP_TYPE = 'http'
57
51
58
52
59 class LazyFormencode(object):
53 class LazyFormencode(object):
60 def __init__(self, formencode_obj, *args, **kwargs):
54 def __init__(self, formencode_obj, *args, **kwargs):
61 self.formencode_obj = formencode_obj
55 self.formencode_obj = formencode_obj
62 self.args = args
56 self.args = args
63 self.kwargs = kwargs
57 self.kwargs = kwargs
64
58
65 def __call__(self, *args, **kwargs):
59 def __call__(self, *args, **kwargs):
66 from inspect import isfunction
60 from inspect import isfunction
67 formencode_obj = self.formencode_obj
61 formencode_obj = self.formencode_obj
68 if isfunction(formencode_obj):
62 if isfunction(formencode_obj):
69 # case we wrap validators into functions
63 # case we wrap validators into functions
70 formencode_obj = self.formencode_obj(*args, **kwargs)
64 formencode_obj = self.formencode_obj(*args, **kwargs)
71 return formencode_obj(*self.args, **self.kwargs)
65 return formencode_obj(*self.args, **self.kwargs)
72
66
73
67
74 class RhodeCodeAuthPluginBase(object):
68 class RhodeCodeAuthPluginBase(object):
75 # cache the authentication request for N amount of seconds. Some kind
69 # cache the authentication request for N amount of seconds. Some kind
76 # of authentication methods are very heavy and it's very efficient to cache
70 # of authentication methods are very heavy and it's very efficient to cache
77 # the result of a call. If it's set to None (default) cache is off
71 # the result of a call. If it's set to None (default) cache is off
78 AUTH_CACHE_TTL = None
72 AUTH_CACHE_TTL = None
79 AUTH_CACHE = {}
73 AUTH_CACHE = {}
80
74
81 auth_func_attrs = {
75 auth_func_attrs = {
82 "username": "unique username",
76 "username": "unique username",
83 "firstname": "first name",
77 "firstname": "first name",
84 "lastname": "last name",
78 "lastname": "last name",
85 "email": "email address",
79 "email": "email address",
86 "groups": '["list", "of", "groups"]',
80 "groups": '["list", "of", "groups"]',
87 "extern_name": "name in external source of record",
81 "extern_name": "name in external source of record",
88 "extern_type": "type of external source of record",
82 "extern_type": "type of external source of record",
89 "admin": 'True|False defines if user should be RhodeCode super admin',
83 "admin": 'True|False defines if user should be RhodeCode super admin',
90 "active":
84 "active":
91 'True|False defines active state of user internally for RhodeCode',
85 'True|False defines active state of user internally for RhodeCode',
92 "active_from_extern":
86 "active_from_extern":
93 "True|False\None, active state from the external auth, "
87 "True|False\None, active state from the external auth, "
94 "None means use definition from RhodeCode extern_type active value"
88 "None means use definition from RhodeCode extern_type active value"
95 }
89 }
96 # set on authenticate() method and via set_auth_type func.
90 # set on authenticate() method and via set_auth_type func.
97 auth_type = None
91 auth_type = None
98
92
99 # List of setting names to store encrypted. Plugins may override this list
93 # List of setting names to store encrypted. Plugins may override this list
100 # to store settings encrypted.
94 # to store settings encrypted.
101 _settings_encrypted = []
95 _settings_encrypted = []
102
96
103 # Mapping of python to DB settings model types. Plugins may override or
97 # Mapping of python to DB settings model types. Plugins may override or
104 # extend this mapping.
98 # extend this mapping.
105 _settings_type_map = {
99 _settings_type_map = {
106 str: 'str',
100 str: 'str',
107 int: 'int',
101 int: 'int',
108 unicode: 'unicode',
102 unicode: 'unicode',
109 bool: 'bool',
103 bool: 'bool',
110 list: 'list',
104 list: 'list',
111 }
105 }
112
106
113 def __init__(self, plugin_id):
107 def __init__(self, plugin_id):
114 self._plugin_id = plugin_id
108 self._plugin_id = plugin_id
115
109
116 def _get_setting_full_name(self, name):
110 def _get_setting_full_name(self, name):
117 """
111 """
118 Return the full setting name used for storing values in the database.
112 Return the full setting name used for storing values in the database.
119 """
113 """
120 # TODO: johbo: Using the name here is problematic. It would be good to
114 # TODO: johbo: Using the name here is problematic. It would be good to
121 # introduce either new models in the database to hold Plugin and
115 # introduce either new models in the database to hold Plugin and
122 # PluginSetting or to use the plugin id here.
116 # PluginSetting or to use the plugin id here.
123 return 'auth_{}_{}'.format(self.name, name)
117 return 'auth_{}_{}'.format(self.name, name)
124
118
125 def _get_setting_type(self, name, value):
119 def _get_setting_type(self, name, value):
126 """
120 """
127 Get the type as used by the SettingsModel accordingly to type of passed
121 Get the type as used by the SettingsModel accordingly to type of passed
128 value. Optionally the suffix `.encrypted` is appended to instruct
122 value. Optionally the suffix `.encrypted` is appended to instruct
129 SettingsModel to store it encrypted.
123 SettingsModel to store it encrypted.
130 """
124 """
131 type_ = self._settings_type_map.get(type(value), 'unicode')
125 type_ = self._settings_type_map.get(type(value), 'unicode')
132 if name in self._settings_encrypted:
126 if name in self._settings_encrypted:
133 type_ = '{}.encrypted'.format(type_)
127 type_ = '{}.encrypted'.format(type_)
134 return type_
128 return type_
135
129
136 def is_enabled(self):
130 def is_enabled(self):
137 """
131 """
138 Returns true if this plugin is enabled. An enabled plugin can be
132 Returns true if this plugin is enabled. An enabled plugin can be
139 configured in the admin interface but it is not consulted during
133 configured in the admin interface but it is not consulted during
140 authentication.
134 authentication.
141 """
135 """
142 auth_plugins = SettingsModel().get_auth_plugins()
136 auth_plugins = SettingsModel().get_auth_plugins()
143 return self.get_id() in auth_plugins
137 return self.get_id() in auth_plugins
144
138
145 def is_active(self):
139 def is_active(self):
146 """
140 """
147 Returns true if the plugin is activated. An activated plugin is
141 Returns true if the plugin is activated. An activated plugin is
148 consulted during authentication, assumed it is also enabled.
142 consulted during authentication, assumed it is also enabled.
149 """
143 """
150 return self.get_setting_by_name('enabled')
144 return self.get_setting_by_name('enabled')
151
145
152 def get_id(self):
146 def get_id(self):
153 """
147 """
154 Returns the plugin id.
148 Returns the plugin id.
155 """
149 """
156 return self._plugin_id
150 return self._plugin_id
157
151
158 def get_display_name(self):
152 def get_display_name(self):
159 """
153 """
160 Returns a translation string for displaying purposes.
154 Returns a translation string for displaying purposes.
161 """
155 """
162 raise NotImplementedError('Not implemented in base class')
156 raise NotImplementedError('Not implemented in base class')
163
157
164 def get_settings_schema(self):
158 def get_settings_schema(self):
165 """
159 """
166 Returns a colander schema, representing the plugin settings.
160 Returns a colander schema, representing the plugin settings.
167 """
161 """
168 return AuthnPluginSettingsSchemaBase()
162 return AuthnPluginSettingsSchemaBase()
169
163
170 def get_setting_by_name(self, name):
164 def get_setting_by_name(self, name):
171 """
165 """
172 Returns a plugin setting by name.
166 Returns a plugin setting by name.
173 """
167 """
174 full_name = self._get_setting_full_name(name)
168 full_name = self._get_setting_full_name(name)
175 db_setting = SettingsModel().get_setting_by_name(full_name)
169 db_setting = SettingsModel().get_setting_by_name(full_name)
176 return db_setting.app_settings_value if db_setting else None
170 return db_setting.app_settings_value if db_setting else None
177
171
178 def create_or_update_setting(self, name, value):
172 def create_or_update_setting(self, name, value):
179 """
173 """
180 Create or update a setting for this plugin in the persistent storage.
174 Create or update a setting for this plugin in the persistent storage.
181 """
175 """
182 full_name = self._get_setting_full_name(name)
176 full_name = self._get_setting_full_name(name)
183 type_ = self._get_setting_type(name, value)
177 type_ = self._get_setting_type(name, value)
184 db_setting = SettingsModel().create_or_update_setting(
178 db_setting = SettingsModel().create_or_update_setting(
185 full_name, value, type_)
179 full_name, value, type_)
186 return db_setting.app_settings_value
180 return db_setting.app_settings_value
187
181
188 def get_settings(self):
182 def get_settings(self):
189 """
183 """
190 Returns the plugin settings as dictionary.
184 Returns the plugin settings as dictionary.
191 """
185 """
192 settings = {}
186 settings = {}
193 for node in self.get_settings_schema():
187 for node in self.get_settings_schema():
194 settings[node.name] = self.get_setting_by_name(node.name)
188 settings[node.name] = self.get_setting_by_name(node.name)
195 return settings
189 return settings
196
190
197 @property
191 @property
198 def validators(self):
192 def validators(self):
199 """
193 """
200 Exposes RhodeCode validators modules
194 Exposes RhodeCode validators modules
201 """
195 """
202 # this is a hack to overcome issues with pylons threadlocals and
196 # this is a hack to overcome issues with pylons threadlocals and
203 # translator object _() not beein registered properly.
197 # translator object _() not beein registered properly.
204 class LazyCaller(object):
198 class LazyCaller(object):
205 def __init__(self, name):
199 def __init__(self, name):
206 self.validator_name = name
200 self.validator_name = name
207
201
208 def __call__(self, *args, **kwargs):
202 def __call__(self, *args, **kwargs):
209 from rhodecode.model import validators as v
203 from rhodecode.model import validators as v
210 obj = getattr(v, self.validator_name)
204 obj = getattr(v, self.validator_name)
211 # log.debug('Initializing lazy formencode object: %s', obj)
205 # log.debug('Initializing lazy formencode object: %s', obj)
212 return LazyFormencode(obj, *args, **kwargs)
206 return LazyFormencode(obj, *args, **kwargs)
213
207
214 class ProxyGet(object):
208 class ProxyGet(object):
215 def __getattribute__(self, name):
209 def __getattribute__(self, name):
216 return LazyCaller(name)
210 return LazyCaller(name)
217
211
218 return ProxyGet()
212 return ProxyGet()
219
213
220 @hybrid_property
214 @hybrid_property
221 def name(self):
215 def name(self):
222 """
216 """
223 Returns the name of this authentication plugin.
217 Returns the name of this authentication plugin.
224
218
225 :returns: string
219 :returns: string
226 """
220 """
227 raise NotImplementedError("Not implemented in base class")
221 raise NotImplementedError("Not implemented in base class")
228
222
223 @property
224 def is_headers_auth(self):
225 """
226 Returns True if this authentication plugin uses HTTP headers as
227 authentication method.
228 """
229 return False
230
229 @hybrid_property
231 @hybrid_property
230 def is_container_auth(self):
232 def is_container_auth(self):
231 """
233 """
232 Returns bool if this module uses container auth.
234 Deprecated method that indicates if this authentication plugin uses
233
235 HTTP headers as authentication method.
234 This property will trigger an automatic call to authenticate on
235 a visit to the website or during a push/pull.
236
237 :returns: bool
238 """
236 """
239 return False
237 warnings.warn(
238 'Use is_headers_auth instead.', category=DeprecationWarning)
239 return self.is_headers_auth
240
240
241 @hybrid_property
241 @hybrid_property
242 def allows_creating_users(self):
242 def allows_creating_users(self):
243 """
243 """
244 Defines if Plugin allows users to be created on-the-fly when
244 Defines if Plugin allows users to be created on-the-fly when
245 authentication is called. Controls how external plugins should behave
245 authentication is called. Controls how external plugins should behave
246 in terms if they are allowed to create new users, or not. Base plugins
246 in terms if they are allowed to create new users, or not. Base plugins
247 should not be allowed to, but External ones should be !
247 should not be allowed to, but External ones should be !
248
248
249 :return: bool
249 :return: bool
250 """
250 """
251 return False
251 return False
252
252
253 def set_auth_type(self, auth_type):
253 def set_auth_type(self, auth_type):
254 self.auth_type = auth_type
254 self.auth_type = auth_type
255
255
256 def allows_authentication_from(
256 def allows_authentication_from(
257 self, user, allows_non_existing_user=True,
257 self, user, allows_non_existing_user=True,
258 allowed_auth_plugins=None, allowed_auth_sources=None):
258 allowed_auth_plugins=None, allowed_auth_sources=None):
259 """
259 """
260 Checks if this authentication module should accept a request for
260 Checks if this authentication module should accept a request for
261 the current user.
261 the current user.
262
262
263 :param user: user object fetched using plugin's get_user() method.
263 :param user: user object fetched using plugin's get_user() method.
264 :param allows_non_existing_user: if True, don't allow the
264 :param allows_non_existing_user: if True, don't allow the
265 user to be empty, meaning not existing in our database
265 user to be empty, meaning not existing in our database
266 :param allowed_auth_plugins: if provided, users extern_type will be
266 :param allowed_auth_plugins: if provided, users extern_type will be
267 checked against a list of provided extern types, which are plugin
267 checked against a list of provided extern types, which are plugin
268 auth_names in the end
268 auth_names in the end
269 :param allowed_auth_sources: authentication type allowed,
269 :param allowed_auth_sources: authentication type allowed,
270 `http` or `vcs` default is both.
270 `http` or `vcs` default is both.
271 defines if plugin will accept only http authentication vcs
271 defines if plugin will accept only http authentication vcs
272 authentication(git/hg) or both
272 authentication(git/hg) or both
273 :returns: boolean
273 :returns: boolean
274 """
274 """
275 if not user and not allows_non_existing_user:
275 if not user and not allows_non_existing_user:
276 log.debug('User is empty but plugin does not allow empty users,'
276 log.debug('User is empty but plugin does not allow empty users,'
277 'not allowed to authenticate')
277 'not allowed to authenticate')
278 return False
278 return False
279
279
280 expected_auth_plugins = allowed_auth_plugins or [self.name]
280 expected_auth_plugins = allowed_auth_plugins or [self.name]
281 if user and (user.extern_type and
281 if user and (user.extern_type and
282 user.extern_type not in expected_auth_plugins):
282 user.extern_type not in expected_auth_plugins):
283 log.debug(
283 log.debug(
284 'User `%s` is bound to `%s` auth type. Plugin allows only '
284 'User `%s` is bound to `%s` auth type. Plugin allows only '
285 '%s, skipping', user, user.extern_type, expected_auth_plugins)
285 '%s, skipping', user, user.extern_type, expected_auth_plugins)
286
286
287 return False
287 return False
288
288
289 # by default accept both
289 # by default accept both
290 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
290 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
291 if self.auth_type not in expected_auth_from:
291 if self.auth_type not in expected_auth_from:
292 log.debug('Current auth source is %s but plugin only allows %s',
292 log.debug('Current auth source is %s but plugin only allows %s',
293 self.auth_type, expected_auth_from)
293 self.auth_type, expected_auth_from)
294 return False
294 return False
295
295
296 return True
296 return True
297
297
298 def get_user(self, username=None, **kwargs):
298 def get_user(self, username=None, **kwargs):
299 """
299 """
300 Helper method for user fetching in plugins, by default it's using
300 Helper method for user fetching in plugins, by default it's using
301 simple fetch by username, but this method can be custimized in plugins
301 simple fetch by username, but this method can be custimized in plugins
302 eg. container auth plugin to fetch user by environ params
302 eg. headers auth plugin to fetch user by environ params
303
303
304 :param username: username if given to fetch from database
304 :param username: username if given to fetch from database
305 :param kwargs: extra arguments needed for user fetching.
305 :param kwargs: extra arguments needed for user fetching.
306 """
306 """
307 user = None
307 user = None
308 log.debug(
308 log.debug(
309 'Trying to fetch user `%s` from RhodeCode database', username)
309 'Trying to fetch user `%s` from RhodeCode database', username)
310 if username:
310 if username:
311 user = User.get_by_username(username)
311 user = User.get_by_username(username)
312 if not user:
312 if not user:
313 log.debug('User not found, fallback to fetch user in '
313 log.debug('User not found, fallback to fetch user in '
314 'case insensitive mode')
314 'case insensitive mode')
315 user = User.get_by_username(username, case_insensitive=True)
315 user = User.get_by_username(username, case_insensitive=True)
316 else:
316 else:
317 log.debug('provided username:`%s` is empty skipping...', username)
317 log.debug('provided username:`%s` is empty skipping...', username)
318 if not user:
318 if not user:
319 log.debug('User `%s` not found in database', username)
319 log.debug('User `%s` not found in database', username)
320 return user
320 return user
321
321
322 def user_activation_state(self):
322 def user_activation_state(self):
323 """
323 """
324 Defines user activation state when creating new users
324 Defines user activation state when creating new users
325
325
326 :returns: boolean
326 :returns: boolean
327 """
327 """
328 raise NotImplementedError("Not implemented in base class")
328 raise NotImplementedError("Not implemented in base class")
329
329
330 def auth(self, userobj, username, passwd, settings, **kwargs):
330 def auth(self, userobj, username, passwd, settings, **kwargs):
331 """
331 """
332 Given a user object (which may be null), username, a plaintext
332 Given a user object (which may be null), username, a plaintext
333 password, and a settings object (containing all the keys needed as
333 password, and a settings object (containing all the keys needed as
334 listed in settings()), authenticate this user's login attempt.
334 listed in settings()), authenticate this user's login attempt.
335
335
336 Return None on failure. On success, return a dictionary of the form:
336 Return None on failure. On success, return a dictionary of the form:
337
337
338 see: RhodeCodeAuthPluginBase.auth_func_attrs
338 see: RhodeCodeAuthPluginBase.auth_func_attrs
339 This is later validated for correctness
339 This is later validated for correctness
340 """
340 """
341 raise NotImplementedError("not implemented in base class")
341 raise NotImplementedError("not implemented in base class")
342
342
343 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
343 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
344 """
344 """
345 Wrapper to call self.auth() that validates call on it
345 Wrapper to call self.auth() that validates call on it
346
346
347 :param userobj: userobj
347 :param userobj: userobj
348 :param username: username
348 :param username: username
349 :param passwd: plaintext password
349 :param passwd: plaintext password
350 :param settings: plugin settings
350 :param settings: plugin settings
351 """
351 """
352 auth = self.auth(userobj, username, passwd, settings, **kwargs)
352 auth = self.auth(userobj, username, passwd, settings, **kwargs)
353 if auth:
353 if auth:
354 # check if hash should be migrated ?
354 # check if hash should be migrated ?
355 new_hash = auth.get('_hash_migrate')
355 new_hash = auth.get('_hash_migrate')
356 if new_hash:
356 if new_hash:
357 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
357 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
358 return self._validate_auth_return(auth)
358 return self._validate_auth_return(auth)
359 return auth
359 return auth
360
360
361 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
361 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
362 new_hash_cypher = _RhodeCodeCryptoBCrypt()
362 new_hash_cypher = _RhodeCodeCryptoBCrypt()
363 # extra checks, so make sure new hash is correct.
363 # extra checks, so make sure new hash is correct.
364 password_encoded = safe_str(password)
364 password_encoded = safe_str(password)
365 if new_hash and new_hash_cypher.hash_check(
365 if new_hash and new_hash_cypher.hash_check(
366 password_encoded, new_hash):
366 password_encoded, new_hash):
367 cur_user = User.get_by_username(username)
367 cur_user = User.get_by_username(username)
368 cur_user.password = new_hash
368 cur_user.password = new_hash
369 Session().add(cur_user)
369 Session().add(cur_user)
370 Session().flush()
370 Session().flush()
371 log.info('Migrated user %s hash to bcrypt', cur_user)
371 log.info('Migrated user %s hash to bcrypt', cur_user)
372
372
373 def _validate_auth_return(self, ret):
373 def _validate_auth_return(self, ret):
374 if not isinstance(ret, dict):
374 if not isinstance(ret, dict):
375 raise Exception('returned value from auth must be a dict')
375 raise Exception('returned value from auth must be a dict')
376 for k in self.auth_func_attrs:
376 for k in self.auth_func_attrs:
377 if k not in ret:
377 if k not in ret:
378 raise Exception('Missing %s attribute from returned data' % k)
378 raise Exception('Missing %s attribute from returned data' % k)
379 return ret
379 return ret
380
380
381
381
382 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
382 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
383
383
384 @hybrid_property
384 @hybrid_property
385 def allows_creating_users(self):
385 def allows_creating_users(self):
386 return True
386 return True
387
387
388 def use_fake_password(self):
388 def use_fake_password(self):
389 """
389 """
390 Return a boolean that indicates whether or not we should set the user's
390 Return a boolean that indicates whether or not we should set the user's
391 password to a random value when it is authenticated by this plugin.
391 password to a random value when it is authenticated by this plugin.
392 If your plugin provides authentication, then you will generally
392 If your plugin provides authentication, then you will generally
393 want this.
393 want this.
394
394
395 :returns: boolean
395 :returns: boolean
396 """
396 """
397 raise NotImplementedError("Not implemented in base class")
397 raise NotImplementedError("Not implemented in base class")
398
398
399 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
399 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
400 # at this point _authenticate calls plugin's `auth()` function
400 # at this point _authenticate calls plugin's `auth()` function
401 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
401 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
402 userobj, username, passwd, settings, **kwargs)
402 userobj, username, passwd, settings, **kwargs)
403 if auth:
403 if auth:
404 # maybe plugin will clean the username ?
404 # maybe plugin will clean the username ?
405 # we should use the return value
405 # we should use the return value
406 username = auth['username']
406 username = auth['username']
407
407
408 # if external source tells us that user is not active, we should
408 # if external source tells us that user is not active, we should
409 # skip rest of the process. This can prevent from creating users in
409 # skip rest of the process. This can prevent from creating users in
410 # RhodeCode when using external authentication, but if it's
410 # RhodeCode when using external authentication, but if it's
411 # inactive user we shouldn't create that user anyway
411 # inactive user we shouldn't create that user anyway
412 if auth['active_from_extern'] is False:
412 if auth['active_from_extern'] is False:
413 log.warning(
413 log.warning(
414 "User %s authenticated against %s, but is inactive",
414 "User %s authenticated against %s, but is inactive",
415 username, self.__module__)
415 username, self.__module__)
416 return None
416 return None
417
417
418 cur_user = User.get_by_username(username, case_insensitive=True)
418 cur_user = User.get_by_username(username, case_insensitive=True)
419 is_user_existing = cur_user is not None
419 is_user_existing = cur_user is not None
420
420
421 if is_user_existing:
421 if is_user_existing:
422 log.debug('Syncing user `%s` from '
422 log.debug('Syncing user `%s` from '
423 '`%s` plugin', username, self.name)
423 '`%s` plugin', username, self.name)
424 else:
424 else:
425 log.debug('Creating non existing user `%s` from '
425 log.debug('Creating non existing user `%s` from '
426 '`%s` plugin', username, self.name)
426 '`%s` plugin', username, self.name)
427
427
428 if self.allows_creating_users:
428 if self.allows_creating_users:
429 log.debug('Plugin `%s` allows to '
429 log.debug('Plugin `%s` allows to '
430 'create new users', self.name)
430 'create new users', self.name)
431 else:
431 else:
432 log.debug('Plugin `%s` does not allow to '
432 log.debug('Plugin `%s` does not allow to '
433 'create new users', self.name)
433 'create new users', self.name)
434
434
435 user_parameters = {
435 user_parameters = {
436 'username': username,
436 'username': username,
437 'email': auth["email"],
437 'email': auth["email"],
438 'firstname': auth["firstname"],
438 'firstname': auth["firstname"],
439 'lastname': auth["lastname"],
439 'lastname': auth["lastname"],
440 'active': auth["active"],
440 'active': auth["active"],
441 'admin': auth["admin"],
441 'admin': auth["admin"],
442 'extern_name': auth["extern_name"],
442 'extern_name': auth["extern_name"],
443 'extern_type': self.name,
443 'extern_type': self.name,
444 'plugin': self,
444 'plugin': self,
445 'allow_to_create_user': self.allows_creating_users,
445 'allow_to_create_user': self.allows_creating_users,
446 }
446 }
447
447
448 if not is_user_existing:
448 if not is_user_existing:
449 if self.use_fake_password():
449 if self.use_fake_password():
450 # Randomize the PW because we don't need it, but don't want
450 # Randomize the PW because we don't need it, but don't want
451 # them blank either
451 # them blank either
452 passwd = PasswordGenerator().gen_password(length=16)
452 passwd = PasswordGenerator().gen_password(length=16)
453 user_parameters['password'] = passwd
453 user_parameters['password'] = passwd
454 else:
454 else:
455 # Since the password is required by create_or_update method of
455 # Since the password is required by create_or_update method of
456 # UserModel, we need to set it explicitly.
456 # UserModel, we need to set it explicitly.
457 # The create_or_update method is smart and recognises the
457 # The create_or_update method is smart and recognises the
458 # password hashes as well.
458 # password hashes as well.
459 user_parameters['password'] = cur_user.password
459 user_parameters['password'] = cur_user.password
460
460
461 # we either create or update users, we also pass the flag
461 # we either create or update users, we also pass the flag
462 # that controls if this method can actually do that.
462 # that controls if this method can actually do that.
463 # raises NotAllowedToCreateUserError if it cannot, and we try to.
463 # raises NotAllowedToCreateUserError if it cannot, and we try to.
464 user = UserModel().create_or_update(**user_parameters)
464 user = UserModel().create_or_update(**user_parameters)
465 Session().flush()
465 Session().flush()
466 # enforce user is just in given groups, all of them has to be ones
466 # enforce user is just in given groups, all of them has to be ones
467 # created from plugins. We store this info in _group_data JSON
467 # created from plugins. We store this info in _group_data JSON
468 # field
468 # field
469 try:
469 try:
470 groups = auth['groups'] or []
470 groups = auth['groups'] or []
471 UserGroupModel().enforce_groups(user, groups, self.name)
471 UserGroupModel().enforce_groups(user, groups, self.name)
472 except Exception:
472 except Exception:
473 # for any reason group syncing fails, we should
473 # for any reason group syncing fails, we should
474 # proceed with login
474 # proceed with login
475 log.error(traceback.format_exc())
475 log.error(traceback.format_exc())
476 Session().commit()
476 Session().commit()
477 return auth
477 return auth
478
478
479
479
480 class AuthomaticBase(RhodeCodeExternalAuthPlugin):
481
482 # TODO: Think about how to create and store this secret string.
483 # We need the secret for the authomatic library. It needs to be the same
484 # across requests.
485 def _get_authomatic_secret(self, length=40):
486 secret = self.get_setting_by_name('secret')
487 if secret is None or secret == 'None' or secret == '':
488 from Crypto import Random, Hash
489 secret_bytes = Random.new().read(length)
490 secret_hash = Hash.SHA256.new()
491 secret_hash.update(secret_bytes)
492 secret = secret_hash.hexdigest()
493 self.create_or_update_setting('secret', secret)
494 Session.commit()
495 secret = self.get_setting_by_name('secret')
496 return secret
497
498 def get_authomatic(self):
499 scope = []
500 if self.name == 'bitbucket':
501 provider_class = oauth1.Bitbucket
502 scope = ['account', 'email', 'repository', 'issue', 'issue:write']
503 elif self.name == 'github':
504 provider_class = oauth2.GitHub
505 scope = ['repo', 'public_repo', 'user:email']
506 elif self.name == 'google':
507 provider_class = oauth2.Google
508 scope = ['profile', 'email']
509 elif self.name == 'twitter':
510 provider_class = oauth1.Twitter
511
512 authomatic_conf = {
513 self.name: {
514 'class_': provider_class,
515 'consumer_key': self.get_setting_by_name('consumer_key'),
516 'consumer_secret': self.get_setting_by_name('consumer_secret'),
517 'scope': scope,
518 'access_headers': {'User-Agent': 'TestAppAgent'},
519 }
520 }
521 secret = self._get_authomatic_secret()
522 return Authomatic(config=authomatic_conf,
523 secret=secret)
524
525 def get_provider_result(self, request):
526 """
527 Provides `authomatic.core.LoginResult` for provider and request
528
529 :param provider_name:
530 :param request:
531 :param config:
532 :return:
533 """
534 response = Response()
535 adapter = WebObAdapter(request, response)
536 authomatic_inst = self.get_authomatic()
537 return authomatic_inst.login(adapter, self.name), response
538
539 def handle_social_data(self, session, user_id, social_data):
540 """
541 Updates user tokens in database whenever necessary
542 :param request:
543 :param user:
544 :param social_data:
545 :return:
546 """
547 if not self.is_active():
548 h.flash(_('This provider is currently disabled'),
549 category='warning')
550 return False
551
552 social_data = social_data
553 update_identity = False
554
555 existing_row = ExternalIdentity.by_external_id_and_provider(
556 social_data['user']['id'],
557 social_data['credentials.provider']
558 )
559
560 if existing_row:
561 Session().delete(existing_row)
562 update_identity = True
563
564 if not existing_row or update_identity:
565 if not update_identity:
566 h.flash(_('Your external identity is now '
567 'connected with your account'), category='success')
568
569 if not social_data['user']['id']:
570 h.flash(_('No external user id found? Perhaps permissions'
571 'for authentication are set incorrectly'),
572 category='error')
573 return False
574
575 ex_identity = ExternalIdentity()
576 ex_identity.external_id = social_data['user']['id']
577 ex_identity.external_username = social_data['user']['user_name']
578 ex_identity.provider_name = social_data['credentials.provider']
579 ex_identity.access_token = social_data['credentials.token']
580 ex_identity.token_secret = social_data['credentials.token_secret']
581 ex_identity.alt_token = social_data['credentials.refresh_token']
582 ex_identity.local_user_id = user_id
583 Session().add(ex_identity)
584 session.pop('rhodecode.social_auth', None)
585 return ex_identity
586
587 def callback_url(self):
588 try:
589 return url('social_auth', provider_name=self.name, qualified=True)
590 except TypeError:
591 pass
592 return ''
593
594
595 def loadplugin(plugin_id):
480 def loadplugin(plugin_id):
596 """
481 """
597 Loads and returns an instantiated authentication plugin.
482 Loads and returns an instantiated authentication plugin.
598 Returns the RhodeCodeAuthPluginBase subclass on success,
483 Returns the RhodeCodeAuthPluginBase subclass on success,
599 raises exceptions on failure.
484 or None on failure.
600
601 raises:
602 KeyError -- if no plugin available with given name
603 TypeError -- if the RhodeCodeAuthPlugin is not a subclass of
604 ours RhodeCodeAuthPluginBase
605 """
485 """
606 # TODO: Disusing pyramids thread locals to retrieve the registry.
486 # TODO: Disusing pyramids thread locals to retrieve the registry.
607 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
487 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
608 plugin = authn_registry.get_plugin(plugin_id)
488 plugin = authn_registry.get_plugin(plugin_id)
609 if plugin is None:
489 if plugin is None:
610 log.error('Authentication plugin not found: "%s"', plugin_id)
490 log.error('Authentication plugin not found: "%s"', plugin_id)
611 return plugin
491 return plugin
612
492
613
493
614 def get_auth_cache_manager(custom_ttl=None):
494 def get_auth_cache_manager(custom_ttl=None):
615 return caches.get_cache_manager(
495 return caches.get_cache_manager(
616 'auth_plugins', 'rhodecode.authentication', custom_ttl)
496 'auth_plugins', 'rhodecode.authentication', custom_ttl)
617
497
618
498
619 def authenticate(username, password, environ=None, auth_type=None,
499 def authenticate(username, password, environ=None, auth_type=None,
620 skip_missing=False):
500 skip_missing=False):
621 """
501 """
622 Authentication function used for access control,
502 Authentication function used for access control,
623 It tries to authenticate based on enabled authentication modules.
503 It tries to authenticate based on enabled authentication modules.
624
504
625 :param username: username can be empty for container auth
505 :param username: username can be empty for headers auth
626 :param password: password can be empty for container auth
506 :param password: password can be empty for headers auth
627 :param environ: environ headers passed for container auth
507 :param environ: environ headers passed for headers auth
628 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
508 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
629 :param skip_missing: ignores plugins that are in db but not in environment
509 :param skip_missing: ignores plugins that are in db but not in environment
630 :returns: None if auth failed, plugin_user dict if auth is correct
510 :returns: None if auth failed, plugin_user dict if auth is correct
631 """
511 """
632 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
512 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
633 raise ValueError('auth type must be on of http, vcs got "%s" instead'
513 raise ValueError('auth type must be on of http, vcs got "%s" instead'
634 % auth_type)
514 % auth_type)
635 container_only = environ and not (username and password)
515 headers_only = environ and not (username and password)
636 auth_plugins = SettingsModel().get_auth_plugins()
637 for plugin_id in auth_plugins:
638 plugin = loadplugin(plugin_id)
639
516
640 if plugin is None:
517 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
641 log.warning('Authentication plugin missing: "{}"'.format(
518 for plugin in authn_registry.get_plugins_for_authentication():
642 plugin_id))
643 continue
644
645 if not plugin.is_active():
646 log.info('Authentication plugin is inactive: "{}"'.format(
647 plugin_id))
648 continue
649
650 plugin.set_auth_type(auth_type)
519 plugin.set_auth_type(auth_type)
651 user = plugin.get_user(username)
520 user = plugin.get_user(username)
652 display_user = user.username if user else username
521 display_user = user.username if user else username
653
522
654 if container_only and not plugin.is_container_auth:
523 if headers_only and not plugin.is_headers_auth:
655 log.debug('Auth type is for container only and plugin `%s` is not '
524 log.debug('Auth type is for headers only and plugin `%s` is not '
656 'container plugin, skipping...', plugin_id)
525 'headers plugin, skipping...', plugin.get_id())
657 continue
526 continue
658
527
659 # load plugin settings from RhodeCode database
528 # load plugin settings from RhodeCode database
660 plugin_settings = plugin.get_settings()
529 plugin_settings = plugin.get_settings()
661 log.debug('Plugin settings:%s', plugin_settings)
530 log.debug('Plugin settings:%s', plugin_settings)
662
531
663 log.debug('Trying authentication using ** %s **', plugin_id)
532 log.debug('Trying authentication using ** %s **', plugin.get_id())
664 # use plugin's method of user extraction.
533 # use plugin's method of user extraction.
665 user = plugin.get_user(username, environ=environ,
534 user = plugin.get_user(username, environ=environ,
666 settings=plugin_settings)
535 settings=plugin_settings)
667 display_user = user.username if user else username
536 display_user = user.username if user else username
668 log.debug('Plugin %s extracted user is `%s`', plugin_id, display_user)
537 log.debug(
538 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
669
539
670 if not plugin.allows_authentication_from(user):
540 if not plugin.allows_authentication_from(user):
671 log.debug('Plugin %s does not accept user `%s` for authentication',
541 log.debug('Plugin %s does not accept user `%s` for authentication',
672 plugin_id, display_user)
542 plugin.get_id(), display_user)
673 continue
543 continue
674 else:
544 else:
675 log.debug('Plugin %s accepted user `%s` for authentication',
545 log.debug('Plugin %s accepted user `%s` for authentication',
676 plugin_id, display_user)
546 plugin.get_id(), display_user)
677
547
678 log.info('Authenticating user `%s` using %s plugin',
548 log.info('Authenticating user `%s` using %s plugin',
679 display_user, plugin_id)
549 display_user, plugin.get_id())
680
550
681 _cache_ttl = 0
551 _cache_ttl = 0
682
552
683 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
553 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
684 # plugin cache set inside is more important than the settings value
554 # plugin cache set inside is more important than the settings value
685 _cache_ttl = plugin.AUTH_CACHE_TTL
555 _cache_ttl = plugin.AUTH_CACHE_TTL
686 elif plugin_settings.get('auth_cache_ttl'):
556 elif plugin_settings.get('auth_cache_ttl'):
687 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
557 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
688
558
689 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
559 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
690
560
691 # get instance of cache manager configured for a namespace
561 # get instance of cache manager configured for a namespace
692 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
562 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
693
563
694 log.debug('Cache for plugin `%s` active: %s', plugin_id,
564 log.debug('Cache for plugin `%s` active: %s', plugin.get_id(),
695 plugin_cache_active)
565 plugin_cache_active)
696
566
697 # for environ based password can be empty, but then the validation is
567 # for environ based password can be empty, but then the validation is
698 # on the server that fills in the env data needed for authentication
568 # on the server that fills in the env data needed for authentication
699 _password_hash = md5_safe(plugin.name + username + (password or ''))
569 _password_hash = md5_safe(plugin.name + username + (password or ''))
700
570
701 # _authenticate is a wrapper for .auth() method of plugin.
571 # _authenticate is a wrapper for .auth() method of plugin.
702 # it checks if .auth() sends proper data.
572 # it checks if .auth() sends proper data.
703 # For RhodeCodeExternalAuthPlugin it also maps users to
573 # For RhodeCodeExternalAuthPlugin it also maps users to
704 # Database and maps the attributes returned from .auth()
574 # Database and maps the attributes returned from .auth()
705 # to RhodeCode database. If this function returns data
575 # to RhodeCode database. If this function returns data
706 # then auth is correct.
576 # then auth is correct.
707 start = time.time()
577 start = time.time()
708 log.debug('Running plugin `%s` _authenticate method',
578 log.debug('Running plugin `%s` _authenticate method',
709 plugin_id)
579 plugin.get_id())
710
580
711 def auth_func():
581 def auth_func():
712 """
582 """
713 This function is used internally in Cache of Beaker to calculate
583 This function is used internally in Cache of Beaker to calculate
714 Results
584 Results
715 """
585 """
716 return plugin._authenticate(
586 return plugin._authenticate(
717 user, username, password, plugin_settings,
587 user, username, password, plugin_settings,
718 environ=environ or {})
588 environ=environ or {})
719
589
720 if plugin_cache_active:
590 if plugin_cache_active:
721 plugin_user = cache_manager.get(
591 plugin_user = cache_manager.get(
722 _password_hash, createfunc=auth_func)
592 _password_hash, createfunc=auth_func)
723 else:
593 else:
724 plugin_user = auth_func()
594 plugin_user = auth_func()
725
595
726 auth_time = time.time() - start
596 auth_time = time.time() - start
727 log.debug('Authentication for plugin `%s` completed in %.3fs, '
597 log.debug('Authentication for plugin `%s` completed in %.3fs, '
728 'expiration time of fetched cache %.1fs.',
598 'expiration time of fetched cache %.1fs.',
729 plugin_id, auth_time, _cache_ttl)
599 plugin.get_id(), auth_time, _cache_ttl)
730
600
731 log.debug('PLUGIN USER DATA: %s', plugin_user)
601 log.debug('PLUGIN USER DATA: %s', plugin_user)
732
602
733 if plugin_user:
603 if plugin_user:
734 log.debug('Plugin returned proper authentication data')
604 log.debug('Plugin returned proper authentication data')
735 return plugin_user
605 return plugin_user
736 # we failed to Auth because .auth() method didn't return proper user
606 # we failed to Auth because .auth() method didn't return proper user
737 log.debug("User `%s` failed to authenticate against %s",
607 log.debug("User `%s` failed to authenticate against %s",
738 display_user, plugin_id)
608 display_user, plugin.get_id())
739 return None
609 return None
@@ -1,276 +1,284 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for Atlassian CROWD
22 RhodeCode authentication plugin for Atlassian CROWD
23 """
23 """
24
24
25
25
26 import colander
26 import colander
27 import base64
27 import base64
28 import logging
28 import logging
29 import urllib2
29 import urllib2
30
30
31 from pylons.i18n.translation import lazy_ugettext as _
31 from pylons.i18n.translation import lazy_ugettext as _
32 from sqlalchemy.ext.hybrid import hybrid_property
32 from sqlalchemy.ext.hybrid import hybrid_property
33
33
34 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.lib.colander_utils import strip_whitespace
37 from rhodecode.lib.ext_json import json, formatted_json
38 from rhodecode.lib.ext_json import json, formatted_json
38 from rhodecode.model.db import User
39 from rhodecode.model.db import User
39
40
40 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
41
42
42
43
43 def plugin_factory(plugin_id, *args, **kwds):
44 def plugin_factory(plugin_id, *args, **kwds):
44 """
45 """
45 Factory function that is called during plugin discovery.
46 Factory function that is called during plugin discovery.
46 It returns the plugin instance.
47 It returns the plugin instance.
47 """
48 """
48 plugin = RhodeCodeAuthPlugin(plugin_id)
49 plugin = RhodeCodeAuthPlugin(plugin_id)
49 return plugin
50 return plugin
50
51
51
52
52 class CrowdAuthnResource(AuthnPluginResourceBase):
53 class CrowdAuthnResource(AuthnPluginResourceBase):
53 pass
54 pass
54
55
55
56
56 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
57 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
57 host = colander.SchemaNode(
58 host = colander.SchemaNode(
58 colander.String(),
59 colander.String(),
59 default='127.0.0.1',
60 default='127.0.0.1',
60 description=_('The FQDN or IP of the Atlassian CROWD Server'),
61 description=_('The FQDN or IP of the Atlassian CROWD Server'),
62 preparer=strip_whitespace,
61 title=_('Host'),
63 title=_('Host'),
62 widget='string')
64 widget='string')
63 port = colander.SchemaNode(
65 port = colander.SchemaNode(
64 colander.Int(),
66 colander.Int(),
65 default=8095,
67 default=8095,
66 description=_('The Port in use by the Atlassian CROWD Server'),
68 description=_('The Port in use by the Atlassian CROWD Server'),
69 preparer=strip_whitespace,
67 title=_('Port'),
70 title=_('Port'),
68 validator=colander.Range(min=0, max=65536),
71 validator=colander.Range(min=0, max=65536),
69 widget='int')
72 widget='int')
70 app_name = colander.SchemaNode(
73 app_name = colander.SchemaNode(
71 colander.String(),
74 colander.String(),
72 default='',
75 default='',
73 description=_('The Application Name to authenticate to CROWD'),
76 description=_('The Application Name to authenticate to CROWD'),
77 preparer=strip_whitespace,
74 title=_('Application Name'),
78 title=_('Application Name'),
75 widget='string')
79 widget='string')
76 app_password = colander.SchemaNode(
80 app_password = colander.SchemaNode(
77 colander.String(),
81 colander.String(),
78 default='',
82 default='',
79 description=_('The password to authenticate to CROWD'),
83 description=_('The password to authenticate to CROWD'),
84 preparer=strip_whitespace,
80 title=_('Application Password'),
85 title=_('Application Password'),
81 widget='password')
86 widget='password')
82 admin_groups = colander.SchemaNode(
87 admin_groups = colander.SchemaNode(
83 colander.String(),
88 colander.String(),
84 default='',
89 default='',
85 description=_('A comma separated list of group names that identify '
90 description=_('A comma separated list of group names that identify '
86 'users as RhodeCode Administrators'),
91 'users as RhodeCode Administrators'),
87 missing='',
92 missing='',
93 preparer=strip_whitespace,
88 title=_('Admin Groups'),
94 title=_('Admin Groups'),
89 widget='string')
95 widget='string')
90
96
91
97
92 class CrowdServer(object):
98 class CrowdServer(object):
93 def __init__(self, *args, **kwargs):
99 def __init__(self, *args, **kwargs):
94 """
100 """
95 Create a new CrowdServer object that points to IP/Address 'host',
101 Create a new CrowdServer object that points to IP/Address 'host',
96 on the given port, and using the given method (https/http). user and
102 on the given port, and using the given method (https/http). user and
97 passwd can be set here or with set_credentials. If unspecified,
103 passwd can be set here or with set_credentials. If unspecified,
98 "version" defaults to "latest".
104 "version" defaults to "latest".
99
105
100 example::
106 example::
101
107
102 cserver = CrowdServer(host="127.0.0.1",
108 cserver = CrowdServer(host="127.0.0.1",
103 port="8095",
109 port="8095",
104 user="some_app",
110 user="some_app",
105 passwd="some_passwd",
111 passwd="some_passwd",
106 version="1")
112 version="1")
107 """
113 """
108 if not "port" in kwargs:
114 if not "port" in kwargs:
109 kwargs["port"] = "8095"
115 kwargs["port"] = "8095"
110 self._logger = kwargs.get("logger", logging.getLogger(__name__))
116 self._logger = kwargs.get("logger", logging.getLogger(__name__))
111 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
117 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
112 kwargs.get("host", "127.0.0.1"),
118 kwargs.get("host", "127.0.0.1"),
113 kwargs.get("port", "8095"))
119 kwargs.get("port", "8095"))
114 self.set_credentials(kwargs.get("user", ""),
120 self.set_credentials(kwargs.get("user", ""),
115 kwargs.get("passwd", ""))
121 kwargs.get("passwd", ""))
116 self._version = kwargs.get("version", "latest")
122 self._version = kwargs.get("version", "latest")
117 self._url_list = None
123 self._url_list = None
118 self._appname = "crowd"
124 self._appname = "crowd"
119
125
120 def set_credentials(self, user, passwd):
126 def set_credentials(self, user, passwd):
121 self.user = user
127 self.user = user
122 self.passwd = passwd
128 self.passwd = passwd
123 self._make_opener()
129 self._make_opener()
124
130
125 def _make_opener(self):
131 def _make_opener(self):
126 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
132 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
127 mgr.add_password(None, self._uri, self.user, self.passwd)
133 mgr.add_password(None, self._uri, self.user, self.passwd)
128 handler = urllib2.HTTPBasicAuthHandler(mgr)
134 handler = urllib2.HTTPBasicAuthHandler(mgr)
129 self.opener = urllib2.build_opener(handler)
135 self.opener = urllib2.build_opener(handler)
130
136
131 def _request(self, url, body=None, headers=None,
137 def _request(self, url, body=None, headers=None,
132 method=None, noformat=False,
138 method=None, noformat=False,
133 empty_response_ok=False):
139 empty_response_ok=False):
134 _headers = {"Content-type": "application/json",
140 _headers = {"Content-type": "application/json",
135 "Accept": "application/json"}
141 "Accept": "application/json"}
136 if self.user and self.passwd:
142 if self.user and self.passwd:
137 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
143 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
138 _headers["Authorization"] = "Basic %s" % authstring
144 _headers["Authorization"] = "Basic %s" % authstring
139 if headers:
145 if headers:
140 _headers.update(headers)
146 _headers.update(headers)
141 log.debug("Sent crowd: \n%s"
147 log.debug("Sent crowd: \n%s"
142 % (formatted_json({"url": url, "body": body,
148 % (formatted_json({"url": url, "body": body,
143 "headers": _headers})))
149 "headers": _headers})))
144 request = urllib2.Request(url, body, _headers)
150 request = urllib2.Request(url, body, _headers)
145 if method:
151 if method:
146 request.get_method = lambda: method
152 request.get_method = lambda: method
147
153
148 global msg
154 global msg
149 msg = ""
155 msg = ""
150 try:
156 try:
151 rdoc = self.opener.open(request)
157 rdoc = self.opener.open(request)
152 msg = "".join(rdoc.readlines())
158 msg = "".join(rdoc.readlines())
153 if not msg and empty_response_ok:
159 if not msg and empty_response_ok:
154 rval = {}
160 rval = {}
155 rval["status"] = True
161 rval["status"] = True
156 rval["error"] = "Response body was empty"
162 rval["error"] = "Response body was empty"
157 elif not noformat:
163 elif not noformat:
158 rval = json.loads(msg)
164 rval = json.loads(msg)
159 rval["status"] = True
165 rval["status"] = True
160 else:
166 else:
161 rval = "".join(rdoc.readlines())
167 rval = "".join(rdoc.readlines())
162 except Exception as e:
168 except Exception as e:
163 if not noformat:
169 if not noformat:
164 rval = {"status": False,
170 rval = {"status": False,
165 "body": body,
171 "body": body,
166 "error": str(e) + "\n" + msg}
172 "error": str(e) + "\n" + msg}
167 else:
173 else:
168 rval = None
174 rval = None
169 return rval
175 return rval
170
176
171 def user_auth(self, username, password):
177 def user_auth(self, username, password):
172 """Authenticate a user against crowd. Returns brief information about
178 """Authenticate a user against crowd. Returns brief information about
173 the user."""
179 the user."""
174 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
180 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
175 % (self._uri, self._version, username))
181 % (self._uri, self._version, username))
176 body = json.dumps({"value": password})
182 body = json.dumps({"value": password})
177 return self._request(url, body)
183 return self._request(url, body)
178
184
179 def user_groups(self, username):
185 def user_groups(self, username):
180 """Retrieve a list of groups to which this user belongs."""
186 """Retrieve a list of groups to which this user belongs."""
181 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
187 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
182 % (self._uri, self._version, username))
188 % (self._uri, self._version, username))
183 return self._request(url)
189 return self._request(url)
184
190
185
191
186 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
192 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
187
193
188 def includeme(self, config):
194 def includeme(self, config):
189 config.add_authn_plugin(self)
195 config.add_authn_plugin(self)
190 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
196 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
191 config.add_view(
197 config.add_view(
192 'rhodecode.authentication.views.AuthnPluginViewBase',
198 'rhodecode.authentication.views.AuthnPluginViewBase',
193 attr='settings_get',
199 attr='settings_get',
200 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
194 request_method='GET',
201 request_method='GET',
195 route_name='auth_home',
202 route_name='auth_home',
196 context=CrowdAuthnResource)
203 context=CrowdAuthnResource)
197 config.add_view(
204 config.add_view(
198 'rhodecode.authentication.views.AuthnPluginViewBase',
205 'rhodecode.authentication.views.AuthnPluginViewBase',
199 attr='settings_post',
206 attr='settings_post',
207 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
200 request_method='POST',
208 request_method='POST',
201 route_name='auth_home',
209 route_name='auth_home',
202 context=CrowdAuthnResource)
210 context=CrowdAuthnResource)
203
211
204 def get_settings_schema(self):
212 def get_settings_schema(self):
205 return CrowdSettingsSchema()
213 return CrowdSettingsSchema()
206
214
207 def get_display_name(self):
215 def get_display_name(self):
208 return _('CROWD')
216 return _('CROWD')
209
217
210 @hybrid_property
218 @hybrid_property
211 def name(self):
219 def name(self):
212 return "crowd"
220 return "crowd"
213
221
214 def use_fake_password(self):
222 def use_fake_password(self):
215 return True
223 return True
216
224
217 def user_activation_state(self):
225 def user_activation_state(self):
218 def_user_perms = User.get_default_user().AuthUser.permissions['global']
226 def_user_perms = User.get_default_user().AuthUser.permissions['global']
219 return 'hg.extern_activate.auto' in def_user_perms
227 return 'hg.extern_activate.auto' in def_user_perms
220
228
221 def auth(self, userobj, username, password, settings, **kwargs):
229 def auth(self, userobj, username, password, settings, **kwargs):
222 """
230 """
223 Given a user object (which may be null), username, a plaintext password,
231 Given a user object (which may be null), username, a plaintext password,
224 and a settings object (containing all the keys needed as listed in settings()),
232 and a settings object (containing all the keys needed as listed in settings()),
225 authenticate this user's login attempt.
233 authenticate this user's login attempt.
226
234
227 Return None on failure. On success, return a dictionary of the form:
235 Return None on failure. On success, return a dictionary of the form:
228
236
229 see: RhodeCodeAuthPluginBase.auth_func_attrs
237 see: RhodeCodeAuthPluginBase.auth_func_attrs
230 This is later validated for correctness
238 This is later validated for correctness
231 """
239 """
232 if not username or not password:
240 if not username or not password:
233 log.debug('Empty username or password skipping...')
241 log.debug('Empty username or password skipping...')
234 return None
242 return None
235
243
236 log.debug("Crowd settings: \n%s" % (formatted_json(settings)))
244 log.debug("Crowd settings: \n%s" % (formatted_json(settings)))
237 server = CrowdServer(**settings)
245 server = CrowdServer(**settings)
238 server.set_credentials(settings["app_name"], settings["app_password"])
246 server.set_credentials(settings["app_name"], settings["app_password"])
239 crowd_user = server.user_auth(username, password)
247 crowd_user = server.user_auth(username, password)
240 log.debug("Crowd returned: \n%s" % (formatted_json(crowd_user)))
248 log.debug("Crowd returned: \n%s" % (formatted_json(crowd_user)))
241 if not crowd_user["status"]:
249 if not crowd_user["status"]:
242 return None
250 return None
243
251
244 res = server.user_groups(crowd_user["name"])
252 res = server.user_groups(crowd_user["name"])
245 log.debug("Crowd groups: \n%s" % (formatted_json(res)))
253 log.debug("Crowd groups: \n%s" % (formatted_json(res)))
246 crowd_user["groups"] = [x["name"] for x in res["groups"]]
254 crowd_user["groups"] = [x["name"] for x in res["groups"]]
247
255
248 # old attrs fetched from RhodeCode database
256 # old attrs fetched from RhodeCode database
249 admin = getattr(userobj, 'admin', False)
257 admin = getattr(userobj, 'admin', False)
250 active = getattr(userobj, 'active', True)
258 active = getattr(userobj, 'active', True)
251 email = getattr(userobj, 'email', '')
259 email = getattr(userobj, 'email', '')
252 username = getattr(userobj, 'username', username)
260 username = getattr(userobj, 'username', username)
253 firstname = getattr(userobj, 'firstname', '')
261 firstname = getattr(userobj, 'firstname', '')
254 lastname = getattr(userobj, 'lastname', '')
262 lastname = getattr(userobj, 'lastname', '')
255 extern_type = getattr(userobj, 'extern_type', '')
263 extern_type = getattr(userobj, 'extern_type', '')
256
264
257 user_attrs = {
265 user_attrs = {
258 'username': username,
266 'username': username,
259 'firstname': crowd_user["first-name"] or firstname,
267 'firstname': crowd_user["first-name"] or firstname,
260 'lastname': crowd_user["last-name"] or lastname,
268 'lastname': crowd_user["last-name"] or lastname,
261 'groups': crowd_user["groups"],
269 'groups': crowd_user["groups"],
262 'email': crowd_user["email"] or email,
270 'email': crowd_user["email"] or email,
263 'admin': admin,
271 'admin': admin,
264 'active': active,
272 'active': active,
265 'active_from_extern': crowd_user.get('active'),
273 'active_from_extern': crowd_user.get('active'),
266 'extern_name': crowd_user["name"],
274 'extern_name': crowd_user["name"],
267 'extern_type': extern_type,
275 'extern_type': extern_type,
268 }
276 }
269
277
270 # set an admin if we're in admin_groups of crowd
278 # set an admin if we're in admin_groups of crowd
271 for group in settings["admin_groups"]:
279 for group in settings["admin_groups"]:
272 if group in user_attrs["groups"]:
280 if group in user_attrs["groups"]:
273 user_attrs["admin"] = True
281 user_attrs["admin"] = True
274 log.debug("Final crowd user object: \n%s" % (formatted_json(user_attrs)))
282 log.debug("Final crowd user object: \n%s" % (formatted_json(user_attrs)))
275 log.info('user %s authenticated correctly' % user_attrs['username'])
283 log.info('user %s authenticated correctly' % user_attrs['username'])
276 return user_attrs
284 return user_attrs
@@ -1,163 +1,167 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for Jasig CAS
22 RhodeCode authentication plugin for Jasig CAS
23 http://www.jasig.org/cas
23 http://www.jasig.org/cas
24 """
24 """
25
25
26
26
27 import colander
27 import colander
28 import logging
28 import logging
29 import rhodecode
29 import rhodecode
30 import urllib
30 import urllib
31 import urllib2
31 import urllib2
32
32
33 from pylons.i18n.translation import lazy_ugettext as _
33 from pylons.i18n.translation import lazy_ugettext as _
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35
35
36 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
36 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 from rhodecode.authentication.routes import AuthnPluginResourceBase
39 from rhodecode.lib.colander_utils import strip_whitespace
39 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.model.db import User
41 from rhodecode.model.db import User
41
42
42 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
43
44
44
45
45 def plugin_factory(plugin_id, *args, **kwds):
46 def plugin_factory(plugin_id, *args, **kwds):
46 """
47 """
47 Factory function that is called during plugin discovery.
48 Factory function that is called during plugin discovery.
48 It returns the plugin instance.
49 It returns the plugin instance.
49 """
50 """
50 plugin = RhodeCodeAuthPlugin(plugin_id)
51 plugin = RhodeCodeAuthPlugin(plugin_id)
51 return plugin
52 return plugin
52
53
53
54
54 class JasigCasAuthnResource(AuthnPluginResourceBase):
55 class JasigCasAuthnResource(AuthnPluginResourceBase):
55 pass
56 pass
56
57
57
58
58 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
59 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
59 service_url = colander.SchemaNode(
60 service_url = colander.SchemaNode(
60 colander.String(),
61 colander.String(),
61 default='https://domain.com/cas/v1/tickets',
62 default='https://domain.com/cas/v1/tickets',
62 description=_('The url of the Jasig CAS REST service'),
63 description=_('The url of the Jasig CAS REST service'),
64 preparer=strip_whitespace,
63 title=_('URL'),
65 title=_('URL'),
64 widget='string')
66 widget='string')
65
67
66
68
67 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
69 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
68
70
69 def includeme(self, config):
71 def includeme(self, config):
70 config.add_authn_plugin(self)
72 config.add_authn_plugin(self)
71 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
73 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
72 config.add_view(
74 config.add_view(
73 'rhodecode.authentication.views.AuthnPluginViewBase',
75 'rhodecode.authentication.views.AuthnPluginViewBase',
74 attr='settings_get',
76 attr='settings_get',
77 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
75 request_method='GET',
78 request_method='GET',
76 route_name='auth_home',
79 route_name='auth_home',
77 context=JasigCasAuthnResource)
80 context=JasigCasAuthnResource)
78 config.add_view(
81 config.add_view(
79 'rhodecode.authentication.views.AuthnPluginViewBase',
82 'rhodecode.authentication.views.AuthnPluginViewBase',
80 attr='settings_post',
83 attr='settings_post',
84 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
81 request_method='POST',
85 request_method='POST',
82 route_name='auth_home',
86 route_name='auth_home',
83 context=JasigCasAuthnResource)
87 context=JasigCasAuthnResource)
84
88
85 def get_settings_schema(self):
89 def get_settings_schema(self):
86 return JasigCasSettingsSchema()
90 return JasigCasSettingsSchema()
87
91
88 def get_display_name(self):
92 def get_display_name(self):
89 return _('Jasig-CAS')
93 return _('Jasig-CAS')
90
94
91 @hybrid_property
95 @hybrid_property
92 def name(self):
96 def name(self):
93 return "jasig-cas"
97 return "jasig-cas"
94
98
95 @hybrid_property
99 @property
96 def is_container_auth(self):
100 def is_headers_auth(self):
97 return True
101 return True
98
102
99 def use_fake_password(self):
103 def use_fake_password(self):
100 return True
104 return True
101
105
102 def user_activation_state(self):
106 def user_activation_state(self):
103 def_user_perms = User.get_default_user().AuthUser.permissions['global']
107 def_user_perms = User.get_default_user().AuthUser.permissions['global']
104 return 'hg.extern_activate.auto' in def_user_perms
108 return 'hg.extern_activate.auto' in def_user_perms
105
109
106 def auth(self, userobj, username, password, settings, **kwargs):
110 def auth(self, userobj, username, password, settings, **kwargs):
107 """
111 """
108 Given a user object (which may be null), username, a plaintext password,
112 Given a user object (which may be null), username, a plaintext password,
109 and a settings object (containing all the keys needed as listed in settings()),
113 and a settings object (containing all the keys needed as listed in settings()),
110 authenticate this user's login attempt.
114 authenticate this user's login attempt.
111
115
112 Return None on failure. On success, return a dictionary of the form:
116 Return None on failure. On success, return a dictionary of the form:
113
117
114 see: RhodeCodeAuthPluginBase.auth_func_attrs
118 see: RhodeCodeAuthPluginBase.auth_func_attrs
115 This is later validated for correctness
119 This is later validated for correctness
116 """
120 """
117 if not username or not password:
121 if not username or not password:
118 log.debug('Empty username or password skipping...')
122 log.debug('Empty username or password skipping...')
119 return None
123 return None
120
124
121 log.debug("Jasig CAS settings: %s", settings)
125 log.debug("Jasig CAS settings: %s", settings)
122 params = urllib.urlencode({'username': username, 'password': password})
126 params = urllib.urlencode({'username': username, 'password': password})
123 headers = {"Content-type": "application/x-www-form-urlencoded",
127 headers = {"Content-type": "application/x-www-form-urlencoded",
124 "Accept": "text/plain",
128 "Accept": "text/plain",
125 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
129 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
126 url = settings["service_url"]
130 url = settings["service_url"]
127
131
128 log.debug("Sent Jasig CAS: \n%s",
132 log.debug("Sent Jasig CAS: \n%s",
129 {"url": url, "body": params, "headers": headers})
133 {"url": url, "body": params, "headers": headers})
130 request = urllib2.Request(url, params, headers)
134 request = urllib2.Request(url, params, headers)
131 try:
135 try:
132 response = urllib2.urlopen(request)
136 response = urllib2.urlopen(request)
133 except urllib2.HTTPError as e:
137 except urllib2.HTTPError as e:
134 log.debug("HTTPError when requesting Jasig CAS (status code: %d)" % e.code)
138 log.debug("HTTPError when requesting Jasig CAS (status code: %d)" % e.code)
135 return None
139 return None
136 except urllib2.URLError as e:
140 except urllib2.URLError as e:
137 log.debug("URLError when requesting Jasig CAS url: %s " % url)
141 log.debug("URLError when requesting Jasig CAS url: %s " % url)
138 return None
142 return None
139
143
140 # old attrs fetched from RhodeCode database
144 # old attrs fetched from RhodeCode database
141 admin = getattr(userobj, 'admin', False)
145 admin = getattr(userobj, 'admin', False)
142 active = getattr(userobj, 'active', True)
146 active = getattr(userobj, 'active', True)
143 email = getattr(userobj, 'email', '')
147 email = getattr(userobj, 'email', '')
144 username = getattr(userobj, 'username', username)
148 username = getattr(userobj, 'username', username)
145 firstname = getattr(userobj, 'firstname', '')
149 firstname = getattr(userobj, 'firstname', '')
146 lastname = getattr(userobj, 'lastname', '')
150 lastname = getattr(userobj, 'lastname', '')
147 extern_type = getattr(userobj, 'extern_type', '')
151 extern_type = getattr(userobj, 'extern_type', '')
148
152
149 user_attrs = {
153 user_attrs = {
150 'username': username,
154 'username': username,
151 'firstname': safe_unicode(firstname or username),
155 'firstname': safe_unicode(firstname or username),
152 'lastname': safe_unicode(lastname or ''),
156 'lastname': safe_unicode(lastname or ''),
153 'groups': [],
157 'groups': [],
154 'email': email or '',
158 'email': email or '',
155 'admin': admin or False,
159 'admin': admin or False,
156 'active': active,
160 'active': active,
157 'active_from_extern': True,
161 'active_from_extern': True,
158 'extern_name': username,
162 'extern_name': username,
159 'extern_type': extern_type,
163 'extern_type': extern_type,
160 }
164 }
161
165
162 log.info('user %s authenticated correctly' % user_attrs['username'])
166 log.info('user %s authenticated correctly' % user_attrs['username'])
163 return user_attrs
167 return user_attrs
@@ -1,447 +1,461 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for LDAP
22 RhodeCode authentication plugin for LDAP
23 """
23 """
24
24
25
25
26 import colander
26 import colander
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from pylons.i18n.translation import lazy_ugettext as _
30 from pylons.i18n.translation import lazy_ugettext as _
31 from sqlalchemy.ext.hybrid import hybrid_property
31 from sqlalchemy.ext.hybrid import hybrid_property
32
32
33 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
33 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 from rhodecode.authentication.routes import AuthnPluginResourceBase
35 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.lib.exceptions import (
37 from rhodecode.lib.exceptions import (
37 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
38 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
38 )
39 )
39 from rhodecode.lib.utils2 import safe_unicode, safe_str
40 from rhodecode.lib.utils2 import safe_unicode, safe_str
40 from rhodecode.model.db import User
41 from rhodecode.model.db import User
41 from rhodecode.model.validators import Missing
42 from rhodecode.model.validators import Missing
42
43
43 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
44
45
45 try:
46 try:
46 import ldap
47 import ldap
47 except ImportError:
48 except ImportError:
48 # means that python-ldap is not installed
49 # means that python-ldap is not installed, we use Missing object to mark
49 ldap = Missing()
50 # ldap lib is Missing
51 ldap = Missing
50
52
51
53
52 def plugin_factory(plugin_id, *args, **kwds):
54 def plugin_factory(plugin_id, *args, **kwds):
53 """
55 """
54 Factory function that is called during plugin discovery.
56 Factory function that is called during plugin discovery.
55 It returns the plugin instance.
57 It returns the plugin instance.
56 """
58 """
57 plugin = RhodeCodeAuthPlugin(plugin_id)
59 plugin = RhodeCodeAuthPlugin(plugin_id)
58 return plugin
60 return plugin
59
61
60
62
61 class LdapAuthnResource(AuthnPluginResourceBase):
63 class LdapAuthnResource(AuthnPluginResourceBase):
62 pass
64 pass
63
65
64
66
65 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
67 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
66 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
68 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
67 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
69 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
68 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
70 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
69
71
70 host = colander.SchemaNode(
72 host = colander.SchemaNode(
71 colander.String(),
73 colander.String(),
72 default='',
74 default='',
73 description=_('Host of the LDAP Server'),
75 description=_('Host of the LDAP Server'),
76 preparer=strip_whitespace,
74 title=_('LDAP Host'),
77 title=_('LDAP Host'),
75 widget='string')
78 widget='string')
76 port = colander.SchemaNode(
79 port = colander.SchemaNode(
77 colander.Int(),
80 colander.Int(),
78 default=389,
81 default=389,
79 description=_('Port that the LDAP server is listening on'),
82 description=_('Port that the LDAP server is listening on'),
83 preparer=strip_whitespace,
80 title=_('Port'),
84 title=_('Port'),
81 validator=colander.Range(min=0, max=65536),
85 validator=colander.Range(min=0, max=65536),
82 widget='int')
86 widget='int')
83 dn_user = colander.SchemaNode(
87 dn_user = colander.SchemaNode(
84 colander.String(),
88 colander.String(),
85 default='',
89 default='',
86 description=_('User to connect to LDAP'),
90 description=_('User to connect to LDAP'),
87 missing='',
91 missing='',
92 preparer=strip_whitespace,
88 title=_('Account'),
93 title=_('Account'),
89 widget='string')
94 widget='string')
90 dn_pass = colander.SchemaNode(
95 dn_pass = colander.SchemaNode(
91 colander.String(),
96 colander.String(),
92 default='',
97 default='',
93 description=_('Password to connect to LDAP'),
98 description=_('Password to connect to LDAP'),
94 missing='',
99 missing='',
100 preparer=strip_whitespace,
95 title=_('Password'),
101 title=_('Password'),
96 widget='password')
102 widget='password')
97 tls_kind = colander.SchemaNode(
103 tls_kind = colander.SchemaNode(
98 colander.String(),
104 colander.String(),
99 default=tls_kind_choices[0],
105 default=tls_kind_choices[0],
100 description=_('TLS Type'),
106 description=_('TLS Type'),
101 title=_('Connection Security'),
107 title=_('Connection Security'),
102 validator=colander.OneOf(tls_kind_choices),
108 validator=colander.OneOf(tls_kind_choices),
103 widget='select')
109 widget='select')
104 tls_reqcert = colander.SchemaNode(
110 tls_reqcert = colander.SchemaNode(
105 colander.String(),
111 colander.String(),
106 default=tls_reqcert_choices[0],
112 default=tls_reqcert_choices[0],
107 description=_('Require Cert over TLS?'),
113 description=_('Require Cert over TLS?'),
108 title=_('Certificate Checks'),
114 title=_('Certificate Checks'),
109 validator=colander.OneOf(tls_reqcert_choices),
115 validator=colander.OneOf(tls_reqcert_choices),
110 widget='select')
116 widget='select')
111 base_dn = colander.SchemaNode(
117 base_dn = colander.SchemaNode(
112 colander.String(),
118 colander.String(),
113 default='',
119 default='',
114 description=_('Base DN to search (e.g., dc=mydomain,dc=com)'),
120 description=_('Base DN to search (e.g., dc=mydomain,dc=com)'),
115 missing='',
121 missing='',
122 preparer=strip_whitespace,
116 title=_('Base DN'),
123 title=_('Base DN'),
117 widget='string')
124 widget='string')
118 filter = colander.SchemaNode(
125 filter = colander.SchemaNode(
119 colander.String(),
126 colander.String(),
120 default='',
127 default='',
121 description=_('Filter to narrow results (e.g., ou=Users, etc)'),
128 description=_('Filter to narrow results (e.g., ou=Users, etc)'),
122 missing='',
129 missing='',
130 preparer=strip_whitespace,
123 title=_('LDAP Search Filter'),
131 title=_('LDAP Search Filter'),
124 widget='string')
132 widget='string')
125 search_scope = colander.SchemaNode(
133 search_scope = colander.SchemaNode(
126 colander.String(),
134 colander.String(),
127 default=search_scope_choices[0],
135 default=search_scope_choices[0],
128 description=_('How deep to search LDAP'),
136 description=_('How deep to search LDAP'),
129 title=_('LDAP Search Scope'),
137 title=_('LDAP Search Scope'),
130 validator=colander.OneOf(search_scope_choices),
138 validator=colander.OneOf(search_scope_choices),
131 widget='select')
139 widget='select')
132 attr_login = colander.SchemaNode(
140 attr_login = colander.SchemaNode(
133 colander.String(),
141 colander.String(),
134 default='',
142 default='',
135 description=_('LDAP Attribute to map to user name'),
143 description=_('LDAP Attribute to map to user name'),
144 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
145 preparer=strip_whitespace,
136 title=_('Login Attribute'),
146 title=_('Login Attribute'),
137 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
138 widget='string')
147 widget='string')
139 attr_firstname = colander.SchemaNode(
148 attr_firstname = colander.SchemaNode(
140 colander.String(),
149 colander.String(),
141 default='',
150 default='',
142 description=_('LDAP Attribute to map to first name'),
151 description=_('LDAP Attribute to map to first name'),
143 missing='',
152 missing='',
153 preparer=strip_whitespace,
144 title=_('First Name Attribute'),
154 title=_('First Name Attribute'),
145 widget='string')
155 widget='string')
146 attr_lastname = colander.SchemaNode(
156 attr_lastname = colander.SchemaNode(
147 colander.String(),
157 colander.String(),
148 default='',
158 default='',
149 description=_('LDAP Attribute to map to last name'),
159 description=_('LDAP Attribute to map to last name'),
150 missing='',
160 missing='',
161 preparer=strip_whitespace,
151 title=_('Last Name Attribute'),
162 title=_('Last Name Attribute'),
152 widget='string')
163 widget='string')
153 attr_email = colander.SchemaNode(
164 attr_email = colander.SchemaNode(
154 colander.String(),
165 colander.String(),
155 default='',
166 default='',
156 description=_('LDAP Attribute to map to email address'),
167 description=_('LDAP Attribute to map to email address'),
157 missing='',
168 missing='',
169 preparer=strip_whitespace,
158 title=_('Email Attribute'),
170 title=_('Email Attribute'),
159 widget='string')
171 widget='string')
160
172
161
173
162 class AuthLdap(object):
174 class AuthLdap(object):
163
175
164 def _build_servers(self):
176 def _build_servers(self):
165 return ', '.join(
177 return ', '.join(
166 ["{}://{}:{}".format(
178 ["{}://{}:{}".format(
167 self.ldap_server_type, host.strip(), self.LDAP_SERVER_PORT)
179 self.ldap_server_type, host.strip(), self.LDAP_SERVER_PORT)
168 for host in self.SERVER_ADDRESSES])
180 for host in self.SERVER_ADDRESSES])
169
181
170 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
182 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
171 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
183 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
172 search_scope='SUBTREE', attr_login='uid',
184 search_scope='SUBTREE', attr_login='uid',
173 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))'):
185 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))'):
174 if isinstance(ldap, Missing):
186 if ldap == Missing:
175 raise LdapImportError("Missing or incompatible ldap library")
187 raise LdapImportError("Missing or incompatible ldap library")
176
188
177 self.ldap_version = ldap_version
189 self.ldap_version = ldap_version
178 self.ldap_server_type = 'ldap'
190 self.ldap_server_type = 'ldap'
179
191
180 self.TLS_KIND = tls_kind
192 self.TLS_KIND = tls_kind
181
193
182 if self.TLS_KIND == 'LDAPS':
194 if self.TLS_KIND == 'LDAPS':
183 port = port or 689
195 port = port or 689
184 self.ldap_server_type += 's'
196 self.ldap_server_type += 's'
185
197
186 OPT_X_TLS_DEMAND = 2
198 OPT_X_TLS_DEMAND = 2
187 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
199 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
188 OPT_X_TLS_DEMAND)
200 OPT_X_TLS_DEMAND)
189 # split server into list
201 # split server into list
190 self.SERVER_ADDRESSES = server.split(',')
202 self.SERVER_ADDRESSES = server.split(',')
191 self.LDAP_SERVER_PORT = port
203 self.LDAP_SERVER_PORT = port
192
204
193 # USE FOR READ ONLY BIND TO LDAP SERVER
205 # USE FOR READ ONLY BIND TO LDAP SERVER
194 self.attr_login = attr_login
206 self.attr_login = attr_login
195
207
196 self.LDAP_BIND_DN = safe_str(bind_dn)
208 self.LDAP_BIND_DN = safe_str(bind_dn)
197 self.LDAP_BIND_PASS = safe_str(bind_pass)
209 self.LDAP_BIND_PASS = safe_str(bind_pass)
198 self.LDAP_SERVER = self._build_servers()
210 self.LDAP_SERVER = self._build_servers()
199 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
211 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
200 self.BASE_DN = safe_str(base_dn)
212 self.BASE_DN = safe_str(base_dn)
201 self.LDAP_FILTER = safe_str(ldap_filter)
213 self.LDAP_FILTER = safe_str(ldap_filter)
202
214
203 def _get_ldap_server(self):
215 def _get_ldap_server(self):
204 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
216 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
205 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
217 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
206 '/etc/openldap/cacerts')
218 '/etc/openldap/cacerts')
207 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
219 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
208 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
220 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
209 ldap.set_option(ldap.OPT_TIMEOUT, 20)
221 ldap.set_option(ldap.OPT_TIMEOUT, 20)
210 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
222 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
211 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
223 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
212 if self.TLS_KIND != 'PLAIN':
224 if self.TLS_KIND != 'PLAIN':
213 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
225 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
214 server = ldap.initialize(self.LDAP_SERVER)
226 server = ldap.initialize(self.LDAP_SERVER)
215 if self.ldap_version == 2:
227 if self.ldap_version == 2:
216 server.protocol = ldap.VERSION2
228 server.protocol = ldap.VERSION2
217 else:
229 else:
218 server.protocol = ldap.VERSION3
230 server.protocol = ldap.VERSION3
219
231
220 if self.TLS_KIND == 'START_TLS':
232 if self.TLS_KIND == 'START_TLS':
221 server.start_tls_s()
233 server.start_tls_s()
222
234
223 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
235 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
224 log.debug('Trying simple_bind with password and given DN: %s',
236 log.debug('Trying simple_bind with password and given DN: %s',
225 self.LDAP_BIND_DN)
237 self.LDAP_BIND_DN)
226 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
238 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
227
239
228 return server
240 return server
229
241
230 def get_uid(self, username):
242 def get_uid(self, username):
231 from rhodecode.lib.helpers import chop_at
243 from rhodecode.lib.helpers import chop_at
232 uid = username
244 uid = username
233 for server_addr in self.SERVER_ADDRESSES:
245 for server_addr in self.SERVER_ADDRESSES:
234 uid = chop_at(username, "@%s" % server_addr)
246 uid = chop_at(username, "@%s" % server_addr)
235 return uid
247 return uid
236
248
237 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
249 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
238 try:
250 try:
239 log.debug('Trying simple bind with %s', dn)
251 log.debug('Trying simple bind with %s', dn)
240 server.simple_bind_s(dn, safe_str(password))
252 server.simple_bind_s(dn, safe_str(password))
241 user = server.search_ext_s(
253 user = server.search_ext_s(
242 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
254 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
243 _, attrs = user
255 _, attrs = user
244 return attrs
256 return attrs
245
257
246 except ldap.INVALID_CREDENTIALS:
258 except ldap.INVALID_CREDENTIALS:
247 log.debug(
259 log.debug(
248 "LDAP rejected password for user '%s': %s, org_exc:",
260 "LDAP rejected password for user '%s': %s, org_exc:",
249 username, dn, exc_info=True)
261 username, dn, exc_info=True)
250
262
251 def authenticate_ldap(self, username, password):
263 def authenticate_ldap(self, username, password):
252 """
264 """
253 Authenticate a user via LDAP and return his/her LDAP properties.
265 Authenticate a user via LDAP and return his/her LDAP properties.
254
266
255 Raises AuthenticationError if the credentials are rejected, or
267 Raises AuthenticationError if the credentials are rejected, or
256 EnvironmentError if the LDAP server can't be reached.
268 EnvironmentError if the LDAP server can't be reached.
257
269
258 :param username: username
270 :param username: username
259 :param password: password
271 :param password: password
260 """
272 """
261
273
262 uid = self.get_uid(username)
274 uid = self.get_uid(username)
263
275
264 if not password:
276 if not password:
265 msg = "Authenticating user %s with blank password not allowed"
277 msg = "Authenticating user %s with blank password not allowed"
266 log.warning(msg, username)
278 log.warning(msg, username)
267 raise LdapPasswordError(msg)
279 raise LdapPasswordError(msg)
268 if "," in username:
280 if "," in username:
269 raise LdapUsernameError("invalid character in username: ,")
281 raise LdapUsernameError("invalid character in username: ,")
270 try:
282 try:
271 server = self._get_ldap_server()
283 server = self._get_ldap_server()
272 filter_ = '(&%s(%s=%s))' % (
284 filter_ = '(&%s(%s=%s))' % (
273 self.LDAP_FILTER, self.attr_login, username)
285 self.LDAP_FILTER, self.attr_login, username)
274 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
286 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
275 filter_, self.LDAP_SERVER)
287 filter_, self.LDAP_SERVER)
276 lobjects = server.search_ext_s(
288 lobjects = server.search_ext_s(
277 self.BASE_DN, self.SEARCH_SCOPE, filter_)
289 self.BASE_DN, self.SEARCH_SCOPE, filter_)
278
290
279 if not lobjects:
291 if not lobjects:
280 raise ldap.NO_SUCH_OBJECT()
292 raise ldap.NO_SUCH_OBJECT()
281
293
282 for (dn, _attrs) in lobjects:
294 for (dn, _attrs) in lobjects:
283 if dn is None:
295 if dn is None:
284 continue
296 continue
285
297
286 user_attrs = self.fetch_attrs_from_simple_bind(
298 user_attrs = self.fetch_attrs_from_simple_bind(
287 server, dn, username, password)
299 server, dn, username, password)
288 if user_attrs:
300 if user_attrs:
289 break
301 break
290
302
291 else:
303 else:
292 log.debug("No matching LDAP objects for authentication "
304 log.debug("No matching LDAP objects for authentication "
293 "of '%s' (%s)", uid, username)
305 "of '%s' (%s)", uid, username)
294 raise LdapPasswordError('Failed to authenticate user '
306 raise LdapPasswordError('Failed to authenticate user '
295 'with given password')
307 'with given password')
296
308
297 except ldap.NO_SUCH_OBJECT:
309 except ldap.NO_SUCH_OBJECT:
298 log.debug("LDAP says no such user '%s' (%s), org_exc:",
310 log.debug("LDAP says no such user '%s' (%s), org_exc:",
299 uid, username, exc_info=True)
311 uid, username, exc_info=True)
300 raise LdapUsernameError()
312 raise LdapUsernameError()
301 except ldap.SERVER_DOWN:
313 except ldap.SERVER_DOWN:
302 org_exc = traceback.format_exc()
314 org_exc = traceback.format_exc()
303 raise LdapConnectionError(
315 raise LdapConnectionError(
304 "LDAP can't access authentication "
316 "LDAP can't access authentication "
305 "server, org_exc:%s" % org_exc)
317 "server, org_exc:%s" % org_exc)
306
318
307 return dn, user_attrs
319 return dn, user_attrs
308
320
309
321
310 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
322 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
311 # used to define dynamic binding in the
323 # used to define dynamic binding in the
312 DYNAMIC_BIND_VAR = '$login'
324 DYNAMIC_BIND_VAR = '$login'
313
325
314 def includeme(self, config):
326 def includeme(self, config):
315 config.add_authn_plugin(self)
327 config.add_authn_plugin(self)
316 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
328 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
317 config.add_view(
329 config.add_view(
318 'rhodecode.authentication.views.AuthnPluginViewBase',
330 'rhodecode.authentication.views.AuthnPluginViewBase',
319 attr='settings_get',
331 attr='settings_get',
332 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
320 request_method='GET',
333 request_method='GET',
321 route_name='auth_home',
334 route_name='auth_home',
322 context=LdapAuthnResource)
335 context=LdapAuthnResource)
323 config.add_view(
336 config.add_view(
324 'rhodecode.authentication.views.AuthnPluginViewBase',
337 'rhodecode.authentication.views.AuthnPluginViewBase',
325 attr='settings_post',
338 attr='settings_post',
339 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
326 request_method='POST',
340 request_method='POST',
327 route_name='auth_home',
341 route_name='auth_home',
328 context=LdapAuthnResource)
342 context=LdapAuthnResource)
329
343
330 def get_settings_schema(self):
344 def get_settings_schema(self):
331 return LdapSettingsSchema()
345 return LdapSettingsSchema()
332
346
333 def get_display_name(self):
347 def get_display_name(self):
334 return _('LDAP')
348 return _('LDAP')
335
349
336 @hybrid_property
350 @hybrid_property
337 def name(self):
351 def name(self):
338 return "ldap"
352 return "ldap"
339
353
340 def use_fake_password(self):
354 def use_fake_password(self):
341 return True
355 return True
342
356
343 def user_activation_state(self):
357 def user_activation_state(self):
344 def_user_perms = User.get_default_user().AuthUser.permissions['global']
358 def_user_perms = User.get_default_user().AuthUser.permissions['global']
345 return 'hg.extern_activate.auto' in def_user_perms
359 return 'hg.extern_activate.auto' in def_user_perms
346
360
347 def try_dynamic_binding(self, username, password, current_args):
361 def try_dynamic_binding(self, username, password, current_args):
348 """
362 """
349 Detects marker inside our original bind, and uses dynamic auth if
363 Detects marker inside our original bind, and uses dynamic auth if
350 present
364 present
351 """
365 """
352
366
353 org_bind = current_args['bind_dn']
367 org_bind = current_args['bind_dn']
354 passwd = current_args['bind_pass']
368 passwd = current_args['bind_pass']
355
369
356 def has_bind_marker(username):
370 def has_bind_marker(username):
357 if self.DYNAMIC_BIND_VAR in username:
371 if self.DYNAMIC_BIND_VAR in username:
358 return True
372 return True
359
373
360 # we only passed in user with "special" variable
374 # we only passed in user with "special" variable
361 if org_bind and has_bind_marker(org_bind) and not passwd:
375 if org_bind and has_bind_marker(org_bind) and not passwd:
362 log.debug('Using dynamic user/password binding for ldap '
376 log.debug('Using dynamic user/password binding for ldap '
363 'authentication. Replacing `%s` with username',
377 'authentication. Replacing `%s` with username',
364 self.DYNAMIC_BIND_VAR)
378 self.DYNAMIC_BIND_VAR)
365 current_args['bind_dn'] = org_bind.replace(
379 current_args['bind_dn'] = org_bind.replace(
366 self.DYNAMIC_BIND_VAR, username)
380 self.DYNAMIC_BIND_VAR, username)
367 current_args['bind_pass'] = password
381 current_args['bind_pass'] = password
368
382
369 return current_args
383 return current_args
370
384
371 def auth(self, userobj, username, password, settings, **kwargs):
385 def auth(self, userobj, username, password, settings, **kwargs):
372 """
386 """
373 Given a user object (which may be null), username, a plaintext password,
387 Given a user object (which may be null), username, a plaintext password,
374 and a settings object (containing all the keys needed as listed in
388 and a settings object (containing all the keys needed as listed in
375 settings()), authenticate this user's login attempt.
389 settings()), authenticate this user's login attempt.
376
390
377 Return None on failure. On success, return a dictionary of the form:
391 Return None on failure. On success, return a dictionary of the form:
378
392
379 see: RhodeCodeAuthPluginBase.auth_func_attrs
393 see: RhodeCodeAuthPluginBase.auth_func_attrs
380 This is later validated for correctness
394 This is later validated for correctness
381 """
395 """
382
396
383 if not username or not password:
397 if not username or not password:
384 log.debug('Empty username or password skipping...')
398 log.debug('Empty username or password skipping...')
385 return None
399 return None
386
400
387 ldap_args = {
401 ldap_args = {
388 'server': settings.get('host', ''),
402 'server': settings.get('host', ''),
389 'base_dn': settings.get('base_dn', ''),
403 'base_dn': settings.get('base_dn', ''),
390 'port': settings.get('port'),
404 'port': settings.get('port'),
391 'bind_dn': settings.get('dn_user'),
405 'bind_dn': settings.get('dn_user'),
392 'bind_pass': settings.get('dn_pass'),
406 'bind_pass': settings.get('dn_pass'),
393 'tls_kind': settings.get('tls_kind'),
407 'tls_kind': settings.get('tls_kind'),
394 'tls_reqcert': settings.get('tls_reqcert'),
408 'tls_reqcert': settings.get('tls_reqcert'),
395 'search_scope': settings.get('search_scope'),
409 'search_scope': settings.get('search_scope'),
396 'attr_login': settings.get('attr_login'),
410 'attr_login': settings.get('attr_login'),
397 'ldap_version': 3,
411 'ldap_version': 3,
398 'ldap_filter': settings.get('filter'),
412 'ldap_filter': settings.get('filter'),
399 }
413 }
400
414
401 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
415 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
402
416
403 log.debug('Checking for ldap authentication.')
417 log.debug('Checking for ldap authentication.')
404
418
405 try:
419 try:
406 aldap = AuthLdap(**ldap_args)
420 aldap = AuthLdap(**ldap_args)
407 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
421 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
408 log.debug('Got ldap DN response %s', user_dn)
422 log.debug('Got ldap DN response %s', user_dn)
409
423
410 def get_ldap_attr(k):
424 def get_ldap_attr(k):
411 return ldap_attrs.get(settings.get(k), [''])[0]
425 return ldap_attrs.get(settings.get(k), [''])[0]
412
426
413 # old attrs fetched from RhodeCode database
427 # old attrs fetched from RhodeCode database
414 admin = getattr(userobj, 'admin', False)
428 admin = getattr(userobj, 'admin', False)
415 active = getattr(userobj, 'active', True)
429 active = getattr(userobj, 'active', True)
416 email = getattr(userobj, 'email', '')
430 email = getattr(userobj, 'email', '')
417 username = getattr(userobj, 'username', username)
431 username = getattr(userobj, 'username', username)
418 firstname = getattr(userobj, 'firstname', '')
432 firstname = getattr(userobj, 'firstname', '')
419 lastname = getattr(userobj, 'lastname', '')
433 lastname = getattr(userobj, 'lastname', '')
420 extern_type = getattr(userobj, 'extern_type', '')
434 extern_type = getattr(userobj, 'extern_type', '')
421
435
422 groups = []
436 groups = []
423 user_attrs = {
437 user_attrs = {
424 'username': username,
438 'username': username,
425 'firstname': safe_unicode(
439 'firstname': safe_unicode(
426 get_ldap_attr('attr_firstname') or firstname),
440 get_ldap_attr('attr_firstname') or firstname),
427 'lastname': safe_unicode(
441 'lastname': safe_unicode(
428 get_ldap_attr('attr_lastname') or lastname),
442 get_ldap_attr('attr_lastname') or lastname),
429 'groups': groups,
443 'groups': groups,
430 'email': get_ldap_attr('attr_email' or email),
444 'email': get_ldap_attr('attr_email' or email),
431 'admin': admin,
445 'admin': admin,
432 'active': active,
446 'active': active,
433 "active_from_extern": None,
447 "active_from_extern": None,
434 'extern_name': user_dn,
448 'extern_name': user_dn,
435 'extern_type': extern_type,
449 'extern_type': extern_type,
436 }
450 }
437 log.debug('ldap user: %s', user_attrs)
451 log.debug('ldap user: %s', user_attrs)
438 log.info('user %s authenticated correctly', user_attrs['username'])
452 log.info('user %s authenticated correctly', user_attrs['username'])
439
453
440 return user_attrs
454 return user_attrs
441
455
442 except (LdapUsernameError, LdapPasswordError, LdapImportError):
456 except (LdapUsernameError, LdapPasswordError, LdapImportError):
443 log.exception("LDAP related exception")
457 log.exception("LDAP related exception")
444 return None
458 return None
445 except (Exception,):
459 except (Exception,):
446 log.exception("Other exception")
460 log.exception("Other exception")
447 return None
461 return None
@@ -1,155 +1,160 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 """
20 """
21 RhodeCode authentication library for PAM
21 RhodeCode authentication library for PAM
22 """
22 """
23
23
24 import colander
24 import colander
25 import grp
25 import grp
26 import logging
26 import logging
27 import pam
27 import pam
28 import pwd
28 import pwd
29 import re
29 import re
30 import socket
30 import socket
31
31
32 from pylons.i18n.translation import lazy_ugettext as _
32 from pylons.i18n.translation import lazy_ugettext as _
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34
34
35 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 from rhodecode.lib.colander_utils import strip_whitespace
38
39
39 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
40
41
41
42
42 def plugin_factory(plugin_id, *args, **kwds):
43 def plugin_factory(plugin_id, *args, **kwds):
43 """
44 """
44 Factory function that is called during plugin discovery.
45 Factory function that is called during plugin discovery.
45 It returns the plugin instance.
46 It returns the plugin instance.
46 """
47 """
47 plugin = RhodeCodeAuthPlugin(plugin_id)
48 plugin = RhodeCodeAuthPlugin(plugin_id)
48 return plugin
49 return plugin
49
50
50
51
51 class PamAuthnResource(AuthnPluginResourceBase):
52 class PamAuthnResource(AuthnPluginResourceBase):
52 pass
53 pass
53
54
54
55
55 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
56 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
56 service = colander.SchemaNode(
57 service = colander.SchemaNode(
57 colander.String(),
58 colander.String(),
58 default='login',
59 default='login',
59 description=_('PAM service name to use for authentication.'),
60 description=_('PAM service name to use for authentication.'),
61 preparer=strip_whitespace,
60 title=_('PAM service name'),
62 title=_('PAM service name'),
61 widget='string')
63 widget='string')
62 gecos = colander.SchemaNode(
64 gecos = colander.SchemaNode(
63 colander.String(),
65 colander.String(),
64 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
66 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
65 description=_('Regular expression for extracting user name/email etc. '
67 description=_('Regular expression for extracting user name/email etc. '
66 'from Unix userinfo.'),
68 'from Unix userinfo.'),
69 preparer=strip_whitespace,
67 title=_('Gecos Regex'),
70 title=_('Gecos Regex'),
68 widget='string')
71 widget='string')
69
72
70
73
71 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
74 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
72 # PAM authentication can be slow. Repository operations involve a lot of
75 # PAM authentication can be slow. Repository operations involve a lot of
73 # auth calls. Little caching helps speedup push/pull operations significantly
76 # auth calls. Little caching helps speedup push/pull operations significantly
74 AUTH_CACHE_TTL = 4
77 AUTH_CACHE_TTL = 4
75
78
76 def includeme(self, config):
79 def includeme(self, config):
77 config.add_authn_plugin(self)
80 config.add_authn_plugin(self)
78 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
81 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
79 config.add_view(
82 config.add_view(
80 'rhodecode.authentication.views.AuthnPluginViewBase',
83 'rhodecode.authentication.views.AuthnPluginViewBase',
81 attr='settings_get',
84 attr='settings_get',
85 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
82 request_method='GET',
86 request_method='GET',
83 route_name='auth_home',
87 route_name='auth_home',
84 context=PamAuthnResource)
88 context=PamAuthnResource)
85 config.add_view(
89 config.add_view(
86 'rhodecode.authentication.views.AuthnPluginViewBase',
90 'rhodecode.authentication.views.AuthnPluginViewBase',
87 attr='settings_post',
91 attr='settings_post',
92 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
88 request_method='POST',
93 request_method='POST',
89 route_name='auth_home',
94 route_name='auth_home',
90 context=PamAuthnResource)
95 context=PamAuthnResource)
91
96
92 def get_display_name(self):
97 def get_display_name(self):
93 return _('PAM')
98 return _('PAM')
94
99
95 @hybrid_property
100 @hybrid_property
96 def name(self):
101 def name(self):
97 return "pam"
102 return "pam"
98
103
99 def get_settings_schema(self):
104 def get_settings_schema(self):
100 return PamSettingsSchema()
105 return PamSettingsSchema()
101
106
102 def use_fake_password(self):
107 def use_fake_password(self):
103 return True
108 return True
104
109
105 def auth(self, userobj, username, password, settings, **kwargs):
110 def auth(self, userobj, username, password, settings, **kwargs):
106 if not username or not password:
111 if not username or not password:
107 log.debug('Empty username or password skipping...')
112 log.debug('Empty username or password skipping...')
108 return None
113 return None
109
114
110 auth_result = pam.authenticate(username, password, settings["service"])
115 auth_result = pam.authenticate(username, password, settings["service"])
111
116
112 if not auth_result:
117 if not auth_result:
113 log.error("PAM was unable to authenticate user: %s" % (username, ))
118 log.error("PAM was unable to authenticate user: %s" % (username, ))
114 return None
119 return None
115
120
116 log.debug('Got PAM response %s' % (auth_result, ))
121 log.debug('Got PAM response %s' % (auth_result, ))
117
122
118 # old attrs fetched from RhodeCode database
123 # old attrs fetched from RhodeCode database
119 default_email = "%s@%s" % (username, socket.gethostname())
124 default_email = "%s@%s" % (username, socket.gethostname())
120 admin = getattr(userobj, 'admin', False)
125 admin = getattr(userobj, 'admin', False)
121 active = getattr(userobj, 'active', True)
126 active = getattr(userobj, 'active', True)
122 email = getattr(userobj, 'email', '') or default_email
127 email = getattr(userobj, 'email', '') or default_email
123 username = getattr(userobj, 'username', username)
128 username = getattr(userobj, 'username', username)
124 firstname = getattr(userobj, 'firstname', '')
129 firstname = getattr(userobj, 'firstname', '')
125 lastname = getattr(userobj, 'lastname', '')
130 lastname = getattr(userobj, 'lastname', '')
126 extern_type = getattr(userobj, 'extern_type', '')
131 extern_type = getattr(userobj, 'extern_type', '')
127
132
128 user_attrs = {
133 user_attrs = {
129 'username': username,
134 'username': username,
130 'firstname': firstname,
135 'firstname': firstname,
131 'lastname': lastname,
136 'lastname': lastname,
132 'groups': [g.gr_name for g in grp.getgrall()
137 'groups': [g.gr_name for g in grp.getgrall()
133 if username in g.gr_mem],
138 if username in g.gr_mem],
134 'email': email,
139 'email': email,
135 'admin': admin,
140 'admin': admin,
136 'active': active,
141 'active': active,
137 'active_from_extern': None,
142 'active_from_extern': None,
138 'extern_name': username,
143 'extern_name': username,
139 'extern_type': extern_type,
144 'extern_type': extern_type,
140 }
145 }
141
146
142 try:
147 try:
143 user_data = pwd.getpwnam(username)
148 user_data = pwd.getpwnam(username)
144 regex = settings["gecos"]
149 regex = settings["gecos"]
145 match = re.search(regex, user_data.pw_gecos)
150 match = re.search(regex, user_data.pw_gecos)
146 if match:
151 if match:
147 user_attrs["firstname"] = match.group('first_name')
152 user_attrs["firstname"] = match.group('first_name')
148 user_attrs["lastname"] = match.group('last_name')
153 user_attrs["lastname"] = match.group('last_name')
149 except Exception:
154 except Exception:
150 log.warning("Cannot extract additional info for PAM user")
155 log.warning("Cannot extract additional info for PAM user")
151 pass
156 pass
152
157
153 log.debug("pamuser: %s", user_attrs)
158 log.debug("pamuser: %s", user_attrs)
154 log.info('user %s authenticated correctly' % user_attrs['username'])
159 log.info('user %s authenticated correctly' % user_attrs['username'])
155 return user_attrs
160 return user_attrs
@@ -1,139 +1,141 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for built in internal auth
22 RhodeCode authentication plugin for built in internal auth
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from pylons.i18n.translation import lazy_ugettext as _
27 from pylons.i18n.translation import lazy_ugettext as _
28 from sqlalchemy.ext.hybrid import hybrid_property
28 from sqlalchemy.ext.hybrid import hybrid_property
29
29
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.lib.utils2 import safe_str
32 from rhodecode.lib.utils2 import safe_str
33 from rhodecode.model.db import User
33 from rhodecode.model.db import User
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 def plugin_factory(plugin_id, *args, **kwds):
38 def plugin_factory(plugin_id, *args, **kwds):
39 plugin = RhodeCodeAuthPlugin(plugin_id)
39 plugin = RhodeCodeAuthPlugin(plugin_id)
40 return plugin
40 return plugin
41
41
42
42
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
44 pass
44 pass
45
45
46
46
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
48
48
49 def includeme(self, config):
49 def includeme(self, config):
50 config.add_authn_plugin(self)
50 config.add_authn_plugin(self)
51 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
51 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
52 config.add_view(
52 config.add_view(
53 'rhodecode.authentication.views.AuthnPluginViewBase',
53 'rhodecode.authentication.views.AuthnPluginViewBase',
54 attr='settings_get',
54 attr='settings_get',
55 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
55 request_method='GET',
56 request_method='GET',
56 route_name='auth_home',
57 route_name='auth_home',
57 context=RhodecodeAuthnResource)
58 context=RhodecodeAuthnResource)
58 config.add_view(
59 config.add_view(
59 'rhodecode.authentication.views.AuthnPluginViewBase',
60 'rhodecode.authentication.views.AuthnPluginViewBase',
60 attr='settings_post',
61 attr='settings_post',
62 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
61 request_method='POST',
63 request_method='POST',
62 route_name='auth_home',
64 route_name='auth_home',
63 context=RhodecodeAuthnResource)
65 context=RhodecodeAuthnResource)
64
66
65 def get_display_name(self):
67 def get_display_name(self):
66 return _('Rhodecode')
68 return _('Rhodecode')
67
69
68 @hybrid_property
70 @hybrid_property
69 def name(self):
71 def name(self):
70 return "rhodecode"
72 return "rhodecode"
71
73
72 def user_activation_state(self):
74 def user_activation_state(self):
73 def_user_perms = User.get_default_user().AuthUser.permissions['global']
75 def_user_perms = User.get_default_user().AuthUser.permissions['global']
74 return 'hg.register.auto_activate' in def_user_perms
76 return 'hg.register.auto_activate' in def_user_perms
75
77
76 def allows_authentication_from(
78 def allows_authentication_from(
77 self, user, allows_non_existing_user=True,
79 self, user, allows_non_existing_user=True,
78 allowed_auth_plugins=None, allowed_auth_sources=None):
80 allowed_auth_plugins=None, allowed_auth_sources=None):
79 """
81 """
80 Custom method for this auth that doesn't accept non existing users.
82 Custom method for this auth that doesn't accept non existing users.
81 We know that user exists in our database.
83 We know that user exists in our database.
82 """
84 """
83 allows_non_existing_user = False
85 allows_non_existing_user = False
84 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
86 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
85 user, allows_non_existing_user=allows_non_existing_user)
87 user, allows_non_existing_user=allows_non_existing_user)
86
88
87 def auth(self, userobj, username, password, settings, **kwargs):
89 def auth(self, userobj, username, password, settings, **kwargs):
88 if not userobj:
90 if not userobj:
89 log.debug('userobj was:%s skipping' % (userobj, ))
91 log.debug('userobj was:%s skipping' % (userobj, ))
90 return None
92 return None
91 if userobj.extern_type != self.name:
93 if userobj.extern_type != self.name:
92 log.warning(
94 log.warning(
93 "userobj:%s extern_type mismatch got:`%s` expected:`%s`" %
95 "userobj:%s extern_type mismatch got:`%s` expected:`%s`" %
94 (userobj, userobj.extern_type, self.name))
96 (userobj, userobj.extern_type, self.name))
95 return None
97 return None
96
98
97 user_attrs = {
99 user_attrs = {
98 "username": userobj.username,
100 "username": userobj.username,
99 "firstname": userobj.firstname,
101 "firstname": userobj.firstname,
100 "lastname": userobj.lastname,
102 "lastname": userobj.lastname,
101 "groups": [],
103 "groups": [],
102 "email": userobj.email,
104 "email": userobj.email,
103 "admin": userobj.admin,
105 "admin": userobj.admin,
104 "active": userobj.active,
106 "active": userobj.active,
105 "active_from_extern": userobj.active,
107 "active_from_extern": userobj.active,
106 "extern_name": userobj.user_id,
108 "extern_name": userobj.user_id,
107 "extern_type": userobj.extern_type,
109 "extern_type": userobj.extern_type,
108 }
110 }
109
111
110 log.debug("User attributes:%s" % (user_attrs, ))
112 log.debug("User attributes:%s" % (user_attrs, ))
111 if userobj.active:
113 if userobj.active:
112 from rhodecode.lib import auth
114 from rhodecode.lib import auth
113 crypto_backend = auth.crypto_backend()
115 crypto_backend = auth.crypto_backend()
114 password_encoded = safe_str(password)
116 password_encoded = safe_str(password)
115 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
117 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
116 password_encoded, userobj.password)
118 password_encoded, userobj.password)
117
119
118 if password_match and new_hash:
120 if password_match and new_hash:
119 log.debug('user %s properly authenticated, but '
121 log.debug('user %s properly authenticated, but '
120 'requires hash change to bcrypt', userobj)
122 'requires hash change to bcrypt', userobj)
121 # if password match, and we use OLD deprecated hash,
123 # if password match, and we use OLD deprecated hash,
122 # we should migrate this user hash password to the new hash
124 # we should migrate this user hash password to the new hash
123 # we store the new returned by hash_check_with_upgrade function
125 # we store the new returned by hash_check_with_upgrade function
124 user_attrs['_hash_migrate'] = new_hash
126 user_attrs['_hash_migrate'] = new_hash
125
127
126 if userobj.username == User.DEFAULT_USER and userobj.active:
128 if userobj.username == User.DEFAULT_USER and userobj.active:
127 log.info(
129 log.info(
128 'user %s authenticated correctly as anonymous user', userobj)
130 'user %s authenticated correctly as anonymous user', userobj)
129 return user_attrs
131 return user_attrs
130
132
131 elif userobj.username == username and password_match:
133 elif userobj.username == username and password_match:
132 log.info('user %s authenticated correctly', userobj)
134 log.info('user %s authenticated correctly', userobj)
133 return user_attrs
135 return user_attrs
134 log.info("user %s had a bad password when "
136 log.info("user %s had a bad password when "
135 "authenticating on this plugin", userobj)
137 "authenticating on this plugin", userobj)
136 return None
138 return None
137 else:
139 else:
138 log.warning('user %s tried auth but is disabled', userobj)
140 log.warning('user %s tried auth but is disabled', userobj)
139 return None
141 return None
@@ -1,53 +1,87 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.exceptions import ConfigurationError
23 from pyramid.exceptions import ConfigurationError
24 from zope.interface import implementer
24 from zope.interface import implementer
25
25
26 from rhodecode.authentication.interface import IAuthnPluginRegistry
26 from rhodecode.authentication.interface import IAuthnPluginRegistry
27 from rhodecode.lib.utils2 import safe_str
27 from rhodecode.lib.utils2 import safe_str
28 from rhodecode.model.settings import SettingsModel
28
29
29 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
30
31
31
32
32 @implementer(IAuthnPluginRegistry)
33 @implementer(IAuthnPluginRegistry)
33 class AuthenticationPluginRegistry(object):
34 class AuthenticationPluginRegistry(object):
34 def __init__(self):
35
36 # INI settings key to set a fallback authentication plugin.
37 fallback_plugin_key = 'rhodecode.auth_plugin_fallback'
38
39 def __init__(self, settings):
35 self._plugins = {}
40 self._plugins = {}
41 self._fallback_plugin = settings.get(self.fallback_plugin_key, None)
36
42
37 def add_authn_plugin(self, config, plugin):
43 def add_authn_plugin(self, config, plugin):
38 plugin_id = plugin.get_id()
44 plugin_id = plugin.get_id()
39 if plugin_id in self._plugins.keys():
45 if plugin_id in self._plugins.keys():
40 raise ConfigurationError(
46 raise ConfigurationError(
41 'Cannot register authentication plugin twice: "%s"', plugin_id)
47 'Cannot register authentication plugin twice: "%s"', plugin_id)
42 else:
48 else:
43 log.debug('Register authentication plugin: "%s"', plugin_id)
49 log.debug('Register authentication plugin: "%s"', plugin_id)
44 self._plugins[plugin_id] = plugin
50 self._plugins[plugin_id] = plugin
45
51
46 def get_plugins(self):
52 def get_plugins(self):
47 def sort_key(plugin):
53 def sort_key(plugin):
48 return str.lower(safe_str(plugin.get_display_name()))
54 return str.lower(safe_str(plugin.get_display_name()))
49
55
50 return sorted(self._plugins.values(), key=sort_key)
56 return sorted(self._plugins.values(), key=sort_key)
51
57
52 def get_plugin(self, plugin_id):
58 def get_plugin(self, plugin_id):
53 return self._plugins.get(plugin_id, None)
59 return self._plugins.get(plugin_id, None)
60
61 def get_plugins_for_authentication(self):
62 """
63 Returns a list of plugins which should be consulted when authenticating
64 a user. It only returns plugins which are enabled and active.
65 Additionally it includes the fallback plugin from the INI file, if
66 `rhodecode.auth_plugin_fallback` is set to a plugin ID.
67 """
68 plugins = []
69
70 # Add all enabled and active plugins to the list. We iterate over the
71 # auth_plugins setting from DB beacuse it also represents the ordering.
72 enabled_plugins = SettingsModel().get_auth_plugins()
73 for plugin_id in enabled_plugins:
74 plugin = self.get_plugin(plugin_id)
75 if plugin is not None and plugin.is_active():
76 plugins.append(plugin)
77
78 # Add the fallback plugin from ini file.
79 if self._fallback_plugin:
80 log.warn(
81 'Using fallback authentication plugin from INI file: "%s"',
82 self._fallback_plugin)
83 plugin = self.get_plugin(self._fallback_plugin)
84 if plugin is not None and plugin not in plugins:
85 plugins.append(plugin)
86
87 return plugins
@@ -1,151 +1,150 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.exceptions import ConfigurationError
23 from pyramid.exceptions import ConfigurationError
24 from pyramid.i18n import TranslationStringFactory
25
24
26 from rhodecode.lib.utils2 import safe_str
25 from rhodecode.lib.utils2 import safe_str
27 from rhodecode.model.settings import SettingsModel
26 from rhodecode.model.settings import SettingsModel
27 from rhodecode.translation import _
28
28
29 _ = TranslationStringFactory('rhodecode-enterprise')
30
29
31 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
32
31
33
32
34 class AuthnResourceBase(object):
33 class AuthnResourceBase(object):
35 __name__ = None
34 __name__ = None
36 __parent__ = None
35 __parent__ = None
37
36
38 def get_root(self):
37 def get_root(self):
39 current = self
38 current = self
40 while current.__parent__ is not None:
39 while current.__parent__ is not None:
41 current = current.__parent__
40 current = current.__parent__
42 return current
41 return current
43
42
44
43
45 class AuthnPluginResourceBase(AuthnResourceBase):
44 class AuthnPluginResourceBase(AuthnResourceBase):
46
45
47 def __init__(self, plugin):
46 def __init__(self, plugin):
48 self.plugin = plugin
47 self.plugin = plugin
49 self.__name__ = plugin.name
48 self.__name__ = plugin.name
50 self.display_name = plugin.get_display_name()
49 self.display_name = plugin.get_display_name()
51
50
52
51
53 class AuthnRootResource(AuthnResourceBase):
52 class AuthnRootResource(AuthnResourceBase):
54 """
53 """
55 This is the root traversal resource object for the authentication settings.
54 This is the root traversal resource object for the authentication settings.
56 """
55 """
57
56
58 def __init__(self):
57 def __init__(self):
59 self._store = {}
58 self._store = {}
60 self._resource_name_map = {}
59 self._resource_name_map = {}
61 self.display_name = _('Global')
60 self.display_name = _('Global')
62
61
63 def __getitem__(self, key):
62 def __getitem__(self, key):
64 """
63 """
65 Customized get item function to return only items (plugins) that are
64 Customized get item function to return only items (plugins) that are
66 activated.
65 activated.
67 """
66 """
68 if self._is_item_active(key):
67 if self._is_item_active(key):
69 return self._store[key]
68 return self._store[key]
70 else:
69 else:
71 raise KeyError('Authentication plugin "{}" is not active.'.format(
70 raise KeyError('Authentication plugin "{}" is not active.'.format(
72 key))
71 key))
73
72
74 def __iter__(self):
73 def __iter__(self):
75 for key in self._store.keys():
74 for key in self._store.keys():
76 if self._is_item_active(key):
75 if self._is_item_active(key):
77 yield self._store[key]
76 yield self._store[key]
78
77
79 def _is_item_active(self, key):
78 def _is_item_active(self, key):
80 activated_plugins = SettingsModel().get_auth_plugins()
79 activated_plugins = SettingsModel().get_auth_plugins()
81 plugin_id = self.get_plugin_id(key)
80 plugin_id = self.get_plugin_id(key)
82 return plugin_id in activated_plugins
81 return plugin_id in activated_plugins
83
82
84 def get_plugin_id(self, resource_name):
83 def get_plugin_id(self, resource_name):
85 """
84 """
86 Return the plugin id for the given traversal resource name.
85 Return the plugin id for the given traversal resource name.
87 """
86 """
88 # TODO: Store this info in the resource element.
87 # TODO: Store this info in the resource element.
89 return self._resource_name_map[resource_name]
88 return self._resource_name_map[resource_name]
90
89
91 def get_sorted_list(self):
90 def get_sorted_list(self):
92 """
91 """
93 Returns a sorted list of sub resources for displaying purposes.
92 Returns a sorted list of sub resources for displaying purposes.
94 """
93 """
95 def sort_key(resource):
94 def sort_key(resource):
96 return str.lower(safe_str(resource.display_name))
95 return str.lower(safe_str(resource.display_name))
97
96
98 active = [item for item in self]
97 active = [item for item in self]
99 return sorted(active, key=sort_key)
98 return sorted(active, key=sort_key)
100
99
101 def get_nav_list(self):
100 def get_nav_list(self):
102 """
101 """
103 Returns a sorted list of resources for displaying the navigation.
102 Returns a sorted list of resources for displaying the navigation.
104 """
103 """
105 list = self.get_sorted_list()
104 list = self.get_sorted_list()
106 list.insert(0, self)
105 list.insert(0, self)
107 return list
106 return list
108
107
109 def add_authn_resource(self, config, plugin_id, resource):
108 def add_authn_resource(self, config, plugin_id, resource):
110 """
109 """
111 Register a traversal resource as a sub element to the authentication
110 Register a traversal resource as a sub element to the authentication
112 settings. This method is registered as a directive on the pyramid
111 settings. This method is registered as a directive on the pyramid
113 configurator object and called by plugins.
112 configurator object and called by plugins.
114 """
113 """
115
114
116 def _ensure_unique_name(name, limit=100):
115 def _ensure_unique_name(name, limit=100):
117 counter = 1
116 counter = 1
118 current = name
117 current = name
119 while current in self._store.keys():
118 while current in self._store.keys():
120 current = '{}{}'.format(name, counter)
119 current = '{}{}'.format(name, counter)
121 counter += 1
120 counter += 1
122 if counter > limit:
121 if counter > limit:
123 raise ConfigurationError(
122 raise ConfigurationError(
124 'Cannot build unique name for traversal resource "%s" '
123 'Cannot build unique name for traversal resource "%s" '
125 'registered by plugin "%s"', name, plugin_id)
124 'registered by plugin "%s"', name, plugin_id)
126 return current
125 return current
127
126
128 # Allow plugin resources with identical names by rename duplicates.
127 # Allow plugin resources with identical names by rename duplicates.
129 unique_name = _ensure_unique_name(resource.__name__)
128 unique_name = _ensure_unique_name(resource.__name__)
130 if unique_name != resource.__name__:
129 if unique_name != resource.__name__:
131 log.warn('Name collision for traversal resource "%s" registered',
130 log.warn('Name collision for traversal resource "%s" registered '
132 'by authentication plugin "%s"', resource.__name__,
131 'by authentication plugin "%s"', resource.__name__,
133 plugin_id)
132 plugin_id)
134 resource.__name__ = unique_name
133 resource.__name__ = unique_name
135
134
136 log.debug('Register traversal resource "%s" for plugin "%s"',
135 log.debug('Register traversal resource "%s" for plugin "%s"',
137 unique_name, plugin_id)
136 unique_name, plugin_id)
138 self._resource_name_map[unique_name] = plugin_id
137 self._resource_name_map[unique_name] = plugin_id
139 resource.__parent__ = self
138 resource.__parent__ = self
140 self._store[unique_name] = resource
139 self._store[unique_name] = resource
141
140
142
141
143 root = AuthnRootResource()
142 root = AuthnRootResource()
144
143
145
144
146 def root_factory(request=None):
145 def root_factory(request=None):
147 """
146 """
148 Returns the root traversal resource instance used for the authentication
147 Returns the root traversal resource instance used for the authentication
149 settings route.
148 settings route.
150 """
149 """
151 return root
150 return root
@@ -1,53 +1,51 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22
22
23 from pyramid.i18n import TranslationStringFactory
23 from rhodecode.translation import _
24
25 _ = TranslationStringFactory('rhodecode-enterprise')
26
24
27
25
28 class AuthnPluginSettingsSchemaBase(colander.MappingSchema):
26 class AuthnPluginSettingsSchemaBase(colander.MappingSchema):
29 """
27 """
30 This base schema is intended for use in authentication plugins.
28 This base schema is intended for use in authentication plugins.
31 It adds a few default settings (e.g., "enabled"), so that plugin
29 It adds a few default settings (e.g., "enabled"), so that plugin
32 authors don't have to maintain a bunch of boilerplate.
30 authors don't have to maintain a bunch of boilerplate.
33 """
31 """
34 enabled = colander.SchemaNode(
32 enabled = colander.SchemaNode(
35 colander.Bool(),
33 colander.Bool(),
36 default=False,
34 default=False,
37 description=_('Enable or disable this authentication plugin.'),
35 description=_('Enable or disable this authentication plugin.'),
38 missing=False,
36 missing=False,
39 title=_('Enabled'),
37 title=_('Enabled'),
40 widget='bool',
38 widget='bool',
41 )
39 )
42 cache_ttl = colander.SchemaNode(
40 cache_ttl = colander.SchemaNode(
43 colander.Int(),
41 colander.Int(),
44 default=0,
42 default=0,
45 description=_('Amount of seconds to cache the authentication '
43 description=_('Amount of seconds to cache the authentication '
46 'call for this plugin. Useful for long calls like '
44 'call for this plugin. Useful for long calls like '
47 'LDAP to improve the responsiveness of the '
45 'LDAP to improve the responsiveness of the '
48 'authentication system (0 means disabled).'),
46 'authentication system (0 means disabled).'),
49 missing=0,
47 missing=0,
50 title=_('Auth Cache TTL'),
48 title=_('Auth Cache TTL'),
51 validator=colander.Range(min=0, max=None),
49 validator=colander.Range(min=0, max=None),
52 widget='int',
50 widget='int',
53 )
51 )
@@ -1,220 +1,182 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 import formencode.htmlfill
22 import formencode.htmlfill
23 import logging
23 import logging
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.i18n import TranslationStringFactory
27 from pyramid.renderers import render
26 from pyramid.renderers import render
28 from pyramid.response import Response
27 from pyramid.response import Response
29
28
30 from rhodecode.authentication.base import get_auth_cache_manager
29 from rhodecode.authentication.base import get_auth_cache_manager
31 from rhodecode.authentication.interface import IAuthnPluginRegistry
30 from rhodecode.authentication.interface import IAuthnPluginRegistry
32 from rhodecode.lib import auth
31 from rhodecode.lib import auth
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.model.forms import AuthSettingsForm
33 from rhodecode.model.forms import AuthSettingsForm
35 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
36 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.settings import SettingsModel
36 from rhodecode.translation import _
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 _ = TranslationStringFactory('rhodecode-enterprise')
41
42
40
43 class AuthnPluginViewBase(object):
41 class AuthnPluginViewBase(object):
44
42
45 def __init__(self, context, request):
43 def __init__(self, context, request):
46 self.request = request
44 self.request = request
47 self.context = context
45 self.context = context
48 self.plugin = context.plugin
46 self.plugin = context.plugin
49
47
50 # TODO: Think about replacing the htmlfill stuff.
48 def settings_get(self, defaults=None, errors=None):
51 def _render_and_fill(self, template, template_context, request,
52 form_defaults, validation_errors):
53 """
54 Helper to render a template and fill the HTML form fields with
55 defaults. Also displays the form errors.
56 """
57 # Render template to string.
58 html = render(template, template_context, request=request)
59
60 # Fill the HTML form fields with default values and add error messages.
61 html = formencode.htmlfill.render(
62 html,
63 defaults=form_defaults,
64 errors=validation_errors,
65 prefix_error=False,
66 encoding="UTF-8",
67 force_defaults=False)
68
69 return html
70
71 def settings_get(self):
72 """
49 """
73 View that displays the plugin settings as a form.
50 View that displays the plugin settings as a form.
74 """
51 """
75 form_defaults = {}
52 defaults = defaults or {}
76 validation_errors = None
53 errors = errors or {}
77 schema = self.plugin.get_settings_schema()
54 schema = self.plugin.get_settings_schema()
78
55
79 # Get default values for the form.
56 # Get default values for the form.
80 for node in schema.children:
57 for node in schema:
81 value = self.plugin.get_setting_by_name(node.name) or node.default
58 db_value = self.plugin.get_setting_by_name(node.name)
82 form_defaults[node.name] = value
59 defaults.setdefault(node.name, db_value)
83
60
84 template_context = {
61 template_context = {
62 'defaults': defaults,
63 'errors': errors,
64 'plugin': self.context.plugin,
85 'resource': self.context,
65 'resource': self.context,
86 'plugin': self.context.plugin
87 }
66 }
88
67
89 return Response(self._render_and_fill(
68 return template_context
90 'rhodecode:templates/admin/auth/plugin_settings.html',
91 template_context,
92 self.request,
93 form_defaults,
94 validation_errors))
95
69
96 def settings_post(self):
70 def settings_post(self):
97 """
71 """
98 View that validates and stores the plugin settings.
72 View that validates and stores the plugin settings.
99 """
73 """
100 schema = self.plugin.get_settings_schema()
74 schema = self.plugin.get_settings_schema()
101 try:
75 try:
102 valid_data = schema.deserialize(self.request.params)
76 valid_data = schema.deserialize(self.request.params)
103 except colander.Invalid, e:
77 except colander.Invalid, e:
104 # Display error message and display form again.
78 # Display error message and display form again.
105 form_defaults = self.request.params
106 validation_errors = e.asdict()
107 self.request.session.flash(
79 self.request.session.flash(
108 _('Errors exist when saving plugin settings. '
80 _('Errors exist when saving plugin settings. '
109 'Please check the form inputs.'),
81 'Please check the form inputs.'),
110 queue='error')
82 queue='error')
111
83 defaults = schema.flatten(self.request.params)
112 template_context = {
84 return self.settings_get(errors=e.asdict(), defaults=defaults)
113 'resource': self.context,
114 'plugin': self.context.plugin
115 }
116
117 return Response(self._render_and_fill(
118 'rhodecode:templates/admin/auth/plugin_settings.html',
119 template_context,
120 self.request,
121 form_defaults,
122 validation_errors))
123
85
124 # Store validated data.
86 # Store validated data.
125 for name, value in valid_data.items():
87 for name, value in valid_data.items():
126 self.plugin.create_or_update_setting(name, value)
88 self.plugin.create_or_update_setting(name, value)
127 Session.commit()
89 Session.commit()
128
90
129 # Display success message and redirect.
91 # Display success message and redirect.
130 self.request.session.flash(
92 self.request.session.flash(
131 _('Auth settings updated successfully.'),
93 _('Auth settings updated successfully.'),
132 queue='success')
94 queue='success')
133 redirect_to = self.request.resource_path(
95 redirect_to = self.request.resource_path(
134 self.context, route_name='auth_home')
96 self.context, route_name='auth_home')
135 return HTTPFound(redirect_to)
97 return HTTPFound(redirect_to)
136
98
137
99
138 # TODO: Ongoing migration in these views.
100 # TODO: Ongoing migration in these views.
139 # - Maybe we should also use a colander schema for these views.
101 # - Maybe we should also use a colander schema for these views.
140 class AuthSettingsView(object):
102 class AuthSettingsView(object):
141 def __init__(self, context, request):
103 def __init__(self, context, request):
142 self.context = context
104 self.context = context
143 self.request = request
105 self.request = request
144
106
145 # TODO: Move this into a utility function. It is needed in all view
107 # TODO: Move this into a utility function. It is needed in all view
146 # classes during migration. Maybe a mixin?
108 # classes during migration. Maybe a mixin?
147
109
148 # Some of the decorators rely on this attribute to be present on the
110 # Some of the decorators rely on this attribute to be present on the
149 # class of the decorated method.
111 # class of the decorated method.
150 self._rhodecode_user = request.user
112 self._rhodecode_user = request.user
151
113
152 @LoginRequired()
114 @LoginRequired()
153 @HasPermissionAllDecorator('hg.admin')
115 @HasPermissionAllDecorator('hg.admin')
154 def index(self, defaults={}, errors=None, prefix_error=False):
116 def index(self, defaults=None, errors=None, prefix_error=False):
117 defaults = defaults or {}
155 authn_registry = self.request.registry.getUtility(IAuthnPluginRegistry)
118 authn_registry = self.request.registry.getUtility(IAuthnPluginRegistry)
156 default_plugins = ['egg:rhodecode-enterprise-ce#rhodecode']
119 enabled_plugins = SettingsModel().get_auth_plugins()
157 enabled_plugins = SettingsModel().get_auth_plugins() or default_plugins
158
120
159 # Create template context and render it.
121 # Create template context and render it.
160 template_context = {
122 template_context = {
161 'resource': self.context,
123 'resource': self.context,
162 'available_plugins': authn_registry.get_plugins(),
124 'available_plugins': authn_registry.get_plugins(),
163 'enabled_plugins': enabled_plugins,
125 'enabled_plugins': enabled_plugins,
164 }
126 }
165 html = render('rhodecode:templates/admin/auth/auth_settings.html',
127 html = render('rhodecode:templates/admin/auth/auth_settings.html',
166 template_context,
128 template_context,
167 request=self.request)
129 request=self.request)
168
130
169 # Create form default values and fill the form.
131 # Create form default values and fill the form.
170 form_defaults = {
132 form_defaults = {
171 'auth_plugins': ','.join(enabled_plugins)
133 'auth_plugins': ','.join(enabled_plugins)
172 }
134 }
173 form_defaults.update(defaults)
135 form_defaults.update(defaults)
174 html = formencode.htmlfill.render(
136 html = formencode.htmlfill.render(
175 html,
137 html,
176 defaults=form_defaults,
138 defaults=form_defaults,
177 errors=errors,
139 errors=errors,
178 prefix_error=prefix_error,
140 prefix_error=prefix_error,
179 encoding="UTF-8",
141 encoding="UTF-8",
180 force_defaults=False)
142 force_defaults=False)
181
143
182 return Response(html)
144 return Response(html)
183
145
184 @LoginRequired()
146 @LoginRequired()
185 @HasPermissionAllDecorator('hg.admin')
147 @HasPermissionAllDecorator('hg.admin')
186 @auth.CSRFRequired()
148 @auth.CSRFRequired()
187 def auth_settings(self):
149 def auth_settings(self):
188 try:
150 try:
189 form = AuthSettingsForm()()
151 form = AuthSettingsForm()()
190 form_result = form.to_python(self.request.params)
152 form_result = form.to_python(self.request.params)
191 plugins = ','.join(form_result['auth_plugins'])
153 plugins = ','.join(form_result['auth_plugins'])
192 setting = SettingsModel().create_or_update_setting(
154 setting = SettingsModel().create_or_update_setting(
193 'auth_plugins', plugins)
155 'auth_plugins', plugins)
194 Session().add(setting)
156 Session().add(setting)
195 Session().commit()
157 Session().commit()
196
158
197 cache_manager = get_auth_cache_manager()
159 cache_manager = get_auth_cache_manager()
198 cache_manager.clear()
160 cache_manager.clear()
199 self.request.session.flash(
161 self.request.session.flash(
200 _('Auth settings updated successfully.'),
162 _('Auth settings updated successfully.'),
201 queue='success')
163 queue='success')
202 except formencode.Invalid as errors:
164 except formencode.Invalid as errors:
203 e = errors.error_dict or {}
165 e = errors.error_dict or {}
204 self.request.session.flash(
166 self.request.session.flash(
205 _('Errors exist when saving plugin setting. '
167 _('Errors exist when saving plugin setting. '
206 'Please check the form inputs.'),
168 'Please check the form inputs.'),
207 queue='error')
169 queue='error')
208 return self.index(
170 return self.index(
209 defaults=errors.value,
171 defaults=errors.value,
210 errors=e,
172 errors=e,
211 prefix_error=False)
173 prefix_error=False)
212 except Exception:
174 except Exception:
213 log.exception('Exception in auth_settings')
175 log.exception('Exception in auth_settings')
214 self.request.session.flash(
176 self.request.session.flash(
215 _('Error occurred during update of auth settings.'),
177 _('Error occurred during update of auth settings.'),
216 queue='error')
178 queue='error')
217
179
218 redirect_to = self.request.resource_path(
180 redirect_to = self.request.resource_path(
219 self.context, route_name='auth_home')
181 self.context, route_name='auth_home')
220 return HTTPFound(redirect_to)
182 return HTTPFound(redirect_to)
@@ -1,181 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons environment configuration
22 Pylons environment configuration
23 """
23 """
24
24
25 import os
25 import os
26 import logging
26 import logging
27 import rhodecode
27 import rhodecode
28 import platform
28 import platform
29 import re
29 import re
30 import io
30
31
31 from mako.lookup import TemplateLookup
32 from mako.lookup import TemplateLookup
32 from pylons.configuration import PylonsConfig
33 from pylons.configuration import PylonsConfig
33 from pylons.error import handle_mako_error
34 from pylons.error import handle_mako_error
35 from pyramid.settings import asbool
34
36
35 # don't remove this import it does magic for celery
37 # don't remove this import it does magic for celery
36 from rhodecode.lib import celerypylons # noqa
38 from rhodecode.lib import celerypylons # noqa
37
39
38 import rhodecode.lib.app_globals as app_globals
40 import rhodecode.lib.app_globals as app_globals
39
41
40 from rhodecode.config import utils
42 from rhodecode.config import utils
41 from rhodecode.config.routing import make_map
43 from rhodecode.config.routing import make_map
44 from rhodecode.config.jsroutes import generate_jsroutes_content
42
45
43 from rhodecode.lib import helpers
46 from rhodecode.lib import helpers
44 from rhodecode.lib.auth import set_available_permissions
47 from rhodecode.lib.auth import set_available_permissions
45 from rhodecode.lib.utils import (
48 from rhodecode.lib.utils import (
46 repo2db_mapper, make_db_config, set_rhodecode_config,
49 repo2db_mapper, make_db_config, set_rhodecode_config,
47 load_rcextensions)
50 load_rcextensions)
48 from rhodecode.lib.utils2 import str2bool, aslist
51 from rhodecode.lib.utils2 import str2bool, aslist
49 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
52 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
50 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
51
54
52 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
53
56
54
55 def load_environment(global_conf, app_conf, initial=False,
57 def load_environment(global_conf, app_conf, initial=False,
56 test_env=None, test_index=None):
58 test_env=None, test_index=None):
57 """
59 """
58 Configure the Pylons environment via the ``pylons.config``
60 Configure the Pylons environment via the ``pylons.config``
59 object
61 object
60 """
62 """
61 config = PylonsConfig()
63 config = PylonsConfig()
62
64
63 rhodecode.is_test = str2bool(app_conf.get('is_test', 'False'))
64
65
65 # Pylons paths
66 # Pylons paths
66 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
67 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
67 paths = {
68 paths = {
68 'root': root,
69 'root': root,
69 'controllers': os.path.join(root, 'controllers'),
70 'controllers': os.path.join(root, 'controllers'),
70 'static_files': os.path.join(root, 'public'),
71 'static_files': os.path.join(root, 'public'),
71 'templates': [os.path.join(root, 'templates')],
72 'templates': [os.path.join(root, 'templates')],
72 }
73 }
73
74
74 # Initialize config with the basic options
75 # Initialize config with the basic options
75 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
76 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
76
77
77 # store some globals into rhodecode
78 # store some globals into rhodecode
78 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
79 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
79 rhodecode.CELERY_EAGER = str2bool(
80 rhodecode.CELERY_EAGER = str2bool(
80 config['app_conf'].get('celery.always.eager'))
81 config['app_conf'].get('celery.always.eager'))
81
82
82 config['routes.map'] = make_map(config)
83 config['routes.map'] = make_map(config)
84
85 if asbool(config['debug']):
86 jsroutes = config['routes.map'].jsroutes()
87 jsroutes_file_content = generate_jsroutes_content(jsroutes)
88 jsroutes_file_path = os.path.join(
89 paths['static_files'], 'js', 'rhodecode', 'routes.js')
90
91 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
92 f.write(jsroutes_file_content)
93
83 config['pylons.app_globals'] = app_globals.Globals(config)
94 config['pylons.app_globals'] = app_globals.Globals(config)
84 config['pylons.h'] = helpers
95 config['pylons.h'] = helpers
85 rhodecode.CONFIG = config
96 rhodecode.CONFIG = config
86
97
87 load_rcextensions(root_path=config['here'])
98 load_rcextensions(root_path=config['here'])
88
99
89 # Setup cache object as early as possible
100 # Setup cache object as early as possible
90 import pylons
101 import pylons
91 pylons.cache._push_object(config['pylons.app_globals'].cache)
102 pylons.cache._push_object(config['pylons.app_globals'].cache)
92
103
93 # Create the Mako TemplateLookup, with the default auto-escaping
104 # Create the Mako TemplateLookup, with the default auto-escaping
94 config['pylons.app_globals'].mako_lookup = TemplateLookup(
105 config['pylons.app_globals'].mako_lookup = TemplateLookup(
95 directories=paths['templates'],
106 directories=paths['templates'],
96 error_handler=handle_mako_error,
107 error_handler=handle_mako_error,
97 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
108 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
98 input_encoding='utf-8', default_filters=['escape'],
109 input_encoding='utf-8', default_filters=['escape'],
99 imports=['from webhelpers.html import escape'])
110 imports=['from webhelpers.html import escape'])
100
111
101 # sets the c attribute access when don't existing attribute are accessed
112 # sets the c attribute access when don't existing attribute are accessed
102 config['pylons.strict_tmpl_context'] = True
113 config['pylons.strict_tmpl_context'] = True
103 config_file_name = os.path.split(config['__file__'])[-1]
104 test = re.match('^test[\w_]*\.ini$', config_file_name) is not None
105 if test:
106 if test_env is None:
107 test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
108
109 from rhodecode.lib.utils import create_test_env, create_test_index
110 from rhodecode.tests import TESTS_TMP_PATH
111 # test repos
112 if test_env:
113 create_test_env(TESTS_TMP_PATH, config)
114 create_test_index(TESTS_TMP_PATH, config, True)
115
114
116 # Limit backends to "vcs.backends" from configuration
115 # Limit backends to "vcs.backends" from configuration
117 backends = config['vcs.backends'] = aslist(
116 backends = config['vcs.backends'] = aslist(
118 config.get('vcs.backends', 'hg,git'), sep=',')
117 config.get('vcs.backends', 'hg,git'), sep=',')
119 for alias in rhodecode.BACKENDS.keys():
118 for alias in rhodecode.BACKENDS.keys():
120 if alias not in backends:
119 if alias not in backends:
121 del rhodecode.BACKENDS[alias]
120 del rhodecode.BACKENDS[alias]
122 log.info("Enabled backends: %s", backends)
121 log.info("Enabled backends: %s", backends)
123
122
124 # initialize vcs client and optionally run the server if enabled
123 # initialize vcs client and optionally run the server if enabled
125 vcs_server_uri = config.get('vcs.server', '')
124 vcs_server_uri = config.get('vcs.server', '')
126 vcs_server_enabled = str2bool(config.get('vcs.server.enable', 'true'))
125 vcs_server_enabled = str2bool(config.get('vcs.server.enable', 'true'))
127 start_server = (
126 start_server = (
128 str2bool(config.get('vcs.start_server', 'false')) and
127 str2bool(config.get('vcs.start_server', 'false')) and
129 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
128 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
130 if vcs_server_enabled and start_server:
129 if vcs_server_enabled and start_server:
131 log.info("Starting vcsserver")
130 log.info("Starting vcsserver")
132 start_vcs_server(server_and_port=vcs_server_uri,
131 start_vcs_server(server_and_port=vcs_server_uri,
133 protocol=utils.get_vcs_server_protocol(config),
132 protocol=utils.get_vcs_server_protocol(config),
134 log_level=config['vcs.server.log_level'])
133 log_level=config['vcs.server.log_level'])
135
134
136 # MULTIPLE DB configs
137 # Setup the SQLAlchemy database engine
138 utils.initialize_database(config)
139
140 set_available_permissions(config)
135 set_available_permissions(config)
141 db_cfg = make_db_config(clear_session=True)
136 db_cfg = make_db_config(clear_session=True)
142
137
143 repos_path = list(db_cfg.items('paths'))[0][1]
138 repos_path = list(db_cfg.items('paths'))[0][1]
144 config['base_path'] = repos_path
139 config['base_path'] = repos_path
145
140
146 config['vcs.hooks.direct_calls'] = _use_direct_hook_calls(config)
141 config['vcs.hooks.direct_calls'] = _use_direct_hook_calls(config)
147 config['vcs.hooks.protocol'] = _get_vcs_hooks_protocol(config)
142 config['vcs.hooks.protocol'] = _get_vcs_hooks_protocol(config)
148
143
149 # store db config also in main global CONFIG
144 # store db config also in main global CONFIG
150 set_rhodecode_config(config)
145 set_rhodecode_config(config)
151
146
152 # configure instance id
147 # configure instance id
153 utils.set_instance_id(config)
148 utils.set_instance_id(config)
154
149
155 # CONFIGURATION OPTIONS HERE (note: all config options will override
150 # CONFIGURATION OPTIONS HERE (note: all config options will override
156 # any Pylons config options)
151 # any Pylons config options)
157
152
158 # store config reference into our module to skip import magic of pylons
153 # store config reference into our module to skip import magic of pylons
159 rhodecode.CONFIG.update(config)
154 rhodecode.CONFIG.update(config)
160
155
161 utils.configure_pyro4(config)
156 utils.configure_pyro4(config)
162 utils.configure_vcs(config)
157 utils.configure_vcs(config)
163 if vcs_server_enabled:
158 if vcs_server_enabled:
164 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(config))
159 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(config))
165
160
166 import_on_startup = str2bool(config.get('startup.import_repos', False))
161 import_on_startup = str2bool(config.get('startup.import_repos', False))
167 if vcs_server_enabled and import_on_startup:
162 if vcs_server_enabled and import_on_startup:
168 repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False)
163 repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False)
169 return config
164 return config
170
165
171
166
172 def _use_direct_hook_calls(config):
167 def _use_direct_hook_calls(config):
173 default_direct_hook_calls = 'false'
168 default_direct_hook_calls = 'false'
174 direct_hook_calls = str2bool(
169 direct_hook_calls = str2bool(
175 config.get('vcs.hooks.direct_calls', default_direct_hook_calls))
170 config.get('vcs.hooks.direct_calls', default_direct_hook_calls))
176 return direct_hook_calls
171 return direct_hook_calls
177
172
178
173
179 def _get_vcs_hooks_protocol(config):
174 def _get_vcs_hooks_protocol(config):
180 protocol = config.get('vcs.hooks.protocol', 'pyro4').lower()
175 protocol = config.get('vcs.hooks.protocol', 'pyro4').lower()
181 return protocol
176 return protocol
177
178
179 def load_pyramid_environment(global_config, settings):
180 # Some parts of the code expect a merge of global and app settings.
181 settings_merged = global_config.copy()
182 settings_merged.update(settings)
183
184 # If this is a test run we prepare the test environment like
185 # creating a test database, test search index and test repositories.
186 # This has to be done before the database connection is initialized.
187 if settings['is_test']:
188 rhodecode.is_test = True
189 utils.initialize_test_environment(settings_merged)
190
191 # Initialize the database connection.
192 utils.initialize_database(settings_merged)
@@ -1,309 +1,315 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons middleware initialization
22 Pylons middleware initialization
23 """
23 """
24 import logging
24 import logging
25
25
26 from paste.registry import RegistryManager
26 from paste.registry import RegistryManager
27 from paste.gzipper import make_gzip_middleware
27 from paste.gzipper import make_gzip_middleware
28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
29 from pylons.wsgiapp import PylonsApp
29 from pylons.wsgiapp import PylonsApp
30 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.static import static_view
32 from pyramid.static import static_view
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.wsgi import wsgiapp
34 from pyramid.wsgi import wsgiapp
35 from routes.middleware import RoutesMiddleware
35 from routes.middleware import RoutesMiddleware
36 import routes.util
36 import routes.util
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config.environment import load_environment
40 from rhodecode.config.environment import (
41 load_environment, load_pyramid_environment)
41 from rhodecode.lib.middleware import csrf
42 from rhodecode.lib.middleware import csrf
42 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
43 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
43 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
44 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
44 from rhodecode.lib.middleware.https_fixup import HttpsFixup
45 from rhodecode.lib.middleware.https_fixup import HttpsFixup
45 from rhodecode.lib.middleware.vcs import VCSMiddleware
46 from rhodecode.lib.middleware.vcs import VCSMiddleware
46 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
47 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
47
48
48
49
49 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
50
51
51
52
52 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
53 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
53 """Create a Pylons WSGI application and return it
54 """Create a Pylons WSGI application and return it
54
55
55 ``global_conf``
56 ``global_conf``
56 The inherited configuration for this application. Normally from
57 The inherited configuration for this application. Normally from
57 the [DEFAULT] section of the Paste ini file.
58 the [DEFAULT] section of the Paste ini file.
58
59
59 ``full_stack``
60 ``full_stack``
60 Whether or not this application provides a full WSGI stack (by
61 Whether or not this application provides a full WSGI stack (by
61 default, meaning it handles its own exceptions and errors).
62 default, meaning it handles its own exceptions and errors).
62 Disable full_stack when this application is "managed" by
63 Disable full_stack when this application is "managed" by
63 another WSGI middleware.
64 another WSGI middleware.
64
65
65 ``app_conf``
66 ``app_conf``
66 The application's local configuration. Normally specified in
67 The application's local configuration. Normally specified in
67 the [app:<name>] section of the Paste ini file (where <name>
68 the [app:<name>] section of the Paste ini file (where <name>
68 defaults to main).
69 defaults to main).
69
70
70 """
71 """
71 # Apply compatibility patches
72 # Apply compatibility patches
72 patches.kombu_1_5_1_python_2_7_11()
73 patches.kombu_1_5_1_python_2_7_11()
73 patches.inspect_getargspec()
74 patches.inspect_getargspec()
74
75
75 # Configure the Pylons environment
76 # Configure the Pylons environment
76 config = load_environment(global_conf, app_conf)
77 config = load_environment(global_conf, app_conf)
77
78
78 # The Pylons WSGI app
79 # The Pylons WSGI app
79 app = PylonsApp(config=config)
80 app = PylonsApp(config=config)
80 if rhodecode.is_test:
81 if rhodecode.is_test:
81 app = csrf.CSRFDetector(app)
82 app = csrf.CSRFDetector(app)
82
83
83 expected_origin = config.get('expected_origin')
84 expected_origin = config.get('expected_origin')
84 if expected_origin:
85 if expected_origin:
85 # The API can be accessed from other Origins.
86 # The API can be accessed from other Origins.
86 app = csrf.OriginChecker(app, expected_origin,
87 app = csrf.OriginChecker(app, expected_origin,
87 skip_urls=[routes.util.url_for('api')])
88 skip_urls=[routes.util.url_for('api')])
88
89
89 # Add RoutesMiddleware. Currently we have two instances in the stack. This
90 # Add RoutesMiddleware. Currently we have two instances in the stack. This
90 # is the lower one to make the StatusCodeRedirect middleware happy.
91 # is the lower one to make the StatusCodeRedirect middleware happy.
91 # TODO: johbo: This is not optimal, search for a better solution.
92 # TODO: johbo: This is not optimal, search for a better solution.
92 app = RoutesMiddleware(app, config['routes.map'])
93 app = RoutesMiddleware(app, config['routes.map'])
93
94
94 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
95 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
95 if asbool(config['pdebug']):
96 if asbool(config['pdebug']):
96 from rhodecode.lib.profiler import ProfilingMiddleware
97 from rhodecode.lib.profiler import ProfilingMiddleware
97 app = ProfilingMiddleware(app)
98 app = ProfilingMiddleware(app)
98
99
99 # Protect from VCS Server error related pages when server is not available
100 # Protect from VCS Server error related pages when server is not available
100 vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true'))
101 vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true'))
101 if not vcs_server_enabled:
102 if not vcs_server_enabled:
102 app = DisableVCSPagesWrapper(app)
103 app = DisableVCSPagesWrapper(app)
103
104
104 if asbool(full_stack):
105 if asbool(full_stack):
105
106
106 # Appenlight monitoring and error handler
107 # Appenlight monitoring and error handler
107 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
108 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
108
109
109 # Handle Python exceptions
110 # Handle Python exceptions
110 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
111 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
111
112
112 # we want our low level middleware to get to the request ASAP. We don't
113 # we want our low level middleware to get to the request ASAP. We don't
113 # need any pylons stack middleware in them
114 # need any pylons stack middleware in them
114 app = VCSMiddleware(app, config, appenlight_client)
115 app = VCSMiddleware(app, config, appenlight_client)
115 # Display error documents for 401, 403, 404 status codes (and
116 # Display error documents for 401, 403, 404 status codes (and
116 # 500 when debug is disabled)
117 # 500 when debug is disabled)
117 if asbool(config['debug']):
118 if asbool(config['debug']):
118 app = StatusCodeRedirect(app)
119 app = StatusCodeRedirect(app)
119 else:
120 else:
120 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
121 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
121
122
122 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
123 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
123 app = HttpsFixup(app, config)
124 app = HttpsFixup(app, config)
124
125
125 # Establish the Registry for this application
126 # Establish the Registry for this application
126 app = RegistryManager(app)
127 app = RegistryManager(app)
127
128
128 app.config = config
129 app.config = config
129
130
130 return app
131 return app
131
132
132
133
133 def make_pyramid_app(global_config, **settings):
134 def make_pyramid_app(global_config, **settings):
134 """
135 """
135 Constructs the WSGI application based on Pyramid and wraps the Pylons based
136 Constructs the WSGI application based on Pyramid and wraps the Pylons based
136 application.
137 application.
137
138
138 Specials:
139 Specials:
139
140
140 * We migrate from Pylons to Pyramid. While doing this, we keep both
141 * We migrate from Pylons to Pyramid. While doing this, we keep both
141 frameworks functional. This involves moving some WSGI middlewares around
142 frameworks functional. This involves moving some WSGI middlewares around
142 and providing access to some data internals, so that the old code is
143 and providing access to some data internals, so that the old code is
143 still functional.
144 still functional.
144
145
145 * The application can also be integrated like a plugin via the call to
146 * The application can also be integrated like a plugin via the call to
146 `includeme`. This is accompanied with the other utility functions which
147 `includeme`. This is accompanied with the other utility functions which
147 are called. Changing this should be done with great care to not break
148 are called. Changing this should be done with great care to not break
148 cases when these fragments are assembled from another place.
149 cases when these fragments are assembled from another place.
149
150
150 """
151 """
151 # The edition string should be available in pylons too, so we add it here
152 # The edition string should be available in pylons too, so we add it here
152 # before copying the settings.
153 # before copying the settings.
153 settings.setdefault('rhodecode.edition', 'Community Edition')
154 settings.setdefault('rhodecode.edition', 'Community Edition')
154
155
155 # As long as our Pylons application does expect "unprepared" settings, make
156 # As long as our Pylons application does expect "unprepared" settings, make
156 # sure that we keep an unmodified copy. This avoids unintentional change of
157 # sure that we keep an unmodified copy. This avoids unintentional change of
157 # behavior in the old application.
158 # behavior in the old application.
158 settings_pylons = settings.copy()
159 settings_pylons = settings.copy()
159
160
160 sanitize_settings_and_apply_defaults(settings)
161 sanitize_settings_and_apply_defaults(settings)
161 config = Configurator(settings=settings)
162 config = Configurator(settings=settings)
162 add_pylons_compat_data(config.registry, global_config, settings_pylons)
163 add_pylons_compat_data(config.registry, global_config, settings_pylons)
164
165 load_pyramid_environment(global_config, settings)
166
163 includeme(config)
167 includeme(config)
164 includeme_last(config)
168 includeme_last(config)
165 pyramid_app = config.make_wsgi_app()
169 pyramid_app = config.make_wsgi_app()
166 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
170 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
167 return pyramid_app
171 return pyramid_app
168
172
169
173
170 def add_pylons_compat_data(registry, global_config, settings):
174 def add_pylons_compat_data(registry, global_config, settings):
171 """
175 """
172 Attach data to the registry to support the Pylons integration.
176 Attach data to the registry to support the Pylons integration.
173 """
177 """
174 registry._pylons_compat_global_config = global_config
178 registry._pylons_compat_global_config = global_config
175 registry._pylons_compat_settings = settings
179 registry._pylons_compat_settings = settings
176
180
177
181
178 def includeme(config):
182 def includeme(config):
179 settings = config.registry.settings
183 settings = config.registry.settings
180
184
181 # Includes which are required. The application would fail without them.
185 # Includes which are required. The application would fail without them.
182 config.include('pyramid_mako')
186 config.include('pyramid_mako')
183 config.include('pyramid_beaker')
187 config.include('pyramid_beaker')
184 config.include('rhodecode.authentication')
188 config.include('rhodecode.authentication')
189 config.include('rhodecode.login')
185 config.include('rhodecode.tweens')
190 config.include('rhodecode.tweens')
186 config.include('rhodecode.api')
191 config.include('rhodecode.api')
187
192
188 # Set the authorization policy.
193 # Set the authorization policy.
189 authz_policy = ACLAuthorizationPolicy()
194 authz_policy = ACLAuthorizationPolicy()
190 config.set_authorization_policy(authz_policy)
195 config.set_authorization_policy(authz_policy)
191
196
192 # Set the default renderer for HTML templates to mako.
197 # Set the default renderer for HTML templates to mako.
193 config.add_mako_renderer('.html')
198 config.add_mako_renderer('.html')
194
199
195 # plugin information
200 # plugin information
196 config.registry.rhodecode_plugins = {}
201 config.registry.rhodecode_plugins = {}
197
202
198 config.add_directive(
203 config.add_directive(
199 'register_rhodecode_plugin', register_rhodecode_plugin)
204 'register_rhodecode_plugin', register_rhodecode_plugin)
200 # include RhodeCode plugins
205 # include RhodeCode plugins
201 includes = aslist(settings.get('rhodecode.includes', []))
206 includes = aslist(settings.get('rhodecode.includes', []))
202 for inc in includes:
207 for inc in includes:
203 config.include(inc)
208 config.include(inc)
204
209
205 # This is the glue which allows us to migrate in chunks. By registering the
210 # This is the glue which allows us to migrate in chunks. By registering the
206 # pylons based application as the "Not Found" view in Pyramid, we will
211 # pylons based application as the "Not Found" view in Pyramid, we will
207 # fallback to the old application each time the new one does not yet know
212 # fallback to the old application each time the new one does not yet know
208 # how to handle a request.
213 # how to handle a request.
209 pylons_app = make_app(
214 pylons_app = make_app(
210 config.registry._pylons_compat_global_config,
215 config.registry._pylons_compat_global_config,
211 **config.registry._pylons_compat_settings)
216 **config.registry._pylons_compat_settings)
212 config.registry._pylons_compat_config = pylons_app.config
217 config.registry._pylons_compat_config = pylons_app.config
213 pylons_app_as_view = wsgiapp(pylons_app)
218 pylons_app_as_view = wsgiapp(pylons_app)
214 config.add_notfound_view(pylons_app_as_view)
219 config.add_notfound_view(pylons_app_as_view)
215
220
216
221
217 def includeme_last(config):
222 def includeme_last(config):
218 """
223 """
219 The static file catchall needs to be last in the view configuration.
224 The static file catchall needs to be last in the view configuration.
220 """
225 """
221 settings = config.registry.settings
226 settings = config.registry.settings
222
227
223 # Note: johbo: I would prefer to register a prefix for static files at some
228 # Note: johbo: I would prefer to register a prefix for static files at some
224 # point, e.g. move them under '_static/'. This would fully avoid that we
229 # point, e.g. move them under '_static/'. This would fully avoid that we
225 # can have name clashes with a repository name. Imaging someone calling his
230 # can have name clashes with a repository name. Imaging someone calling his
226 # repo "css" ;-) Also having an external web server to serve out the static
231 # repo "css" ;-) Also having an external web server to serve out the static
227 # files seems to be easier to set up if they have a common prefix.
232 # files seems to be easier to set up if they have a common prefix.
228 #
233 #
229 # Example: config.add_static_view('_static', path='rhodecode:public')
234 # Example: config.add_static_view('_static', path='rhodecode:public')
230 #
235 #
231 # It might be an option to register both paths for a while and then migrate
236 # It might be an option to register both paths for a while and then migrate
232 # over to the new location.
237 # over to the new location.
233
238
234 # Serving static files with a catchall.
239 # Serving static files with a catchall.
235 if settings['static_files']:
240 if settings['static_files']:
236 config.add_route('catchall_static', '/*subpath')
241 config.add_route('catchall_static', '/*subpath')
237 config.add_view(
242 config.add_view(
238 static_view('rhodecode:public'), route_name='catchall_static')
243 static_view('rhodecode:public'), route_name='catchall_static')
239
244
240
245
241 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
246 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
242 """
247 """
243 Apply outer WSGI middlewares around the application.
248 Apply outer WSGI middlewares around the application.
244
249
245 Part of this has been moved up from the Pylons layer, so that the
250 Part of this has been moved up from the Pylons layer, so that the
246 data is also available if old Pylons code is hit through an already ported
251 data is also available if old Pylons code is hit through an already ported
247 view.
252 view.
248 """
253 """
249 settings = config.registry.settings
254 settings = config.registry.settings
250
255
251 # Add RoutesMiddleware. Currently we have two instances in the stack. This
256 # Add RoutesMiddleware. Currently we have two instances in the stack. This
252 # is the upper one to support the pylons compatibility tween during
257 # is the upper one to support the pylons compatibility tween during
253 # migration to pyramid.
258 # migration to pyramid.
254 pyramid_app = RoutesMiddleware(
259 pyramid_app = RoutesMiddleware(
255 pyramid_app, config.registry._pylons_compat_config['routes.map'])
260 pyramid_app, config.registry._pylons_compat_config['routes.map'])
256
261
257 # TODO: johbo: Don't really see why we enable the gzip middleware when
262 # TODO: johbo: Don't really see why we enable the gzip middleware when
258 # serving static files, might be something that should have its own setting
263 # serving static files, might be something that should have its own setting
259 # as well?
264 # as well?
260 if settings['static_files']:
265 if settings['static_files']:
261 pyramid_app = make_gzip_middleware(
266 pyramid_app = make_gzip_middleware(
262 pyramid_app, settings, compress_level=1)
267 pyramid_app, settings, compress_level=1)
263
268
264 return pyramid_app
269 return pyramid_app
265
270
266
271
267 def sanitize_settings_and_apply_defaults(settings):
272 def sanitize_settings_and_apply_defaults(settings):
268 """
273 """
269 Applies settings defaults and does all type conversion.
274 Applies settings defaults and does all type conversion.
270
275
271 We would move all settings parsing and preparation into this place, so that
276 We would move all settings parsing and preparation into this place, so that
272 we have only one place left which deals with this part. The remaining parts
277 we have only one place left which deals with this part. The remaining parts
273 of the application would start to rely fully on well prepared settings.
278 of the application would start to rely fully on well prepared settings.
274
279
275 This piece would later be split up per topic to avoid a big fat monster
280 This piece would later be split up per topic to avoid a big fat monster
276 function.
281 function.
277 """
282 """
278
283
279 # Pyramid's mako renderer has to search in the templates folder so that the
284 # Pyramid's mako renderer has to search in the templates folder so that the
280 # old templates still work. Ported and new templates are expected to use
285 # old templates still work. Ported and new templates are expected to use
281 # real asset specifications for the includes.
286 # real asset specifications for the includes.
282 mako_directories = settings.setdefault('mako.directories', [
287 mako_directories = settings.setdefault('mako.directories', [
283 # Base templates of the original Pylons application
288 # Base templates of the original Pylons application
284 'rhodecode:templates',
289 'rhodecode:templates',
285 ])
290 ])
286 log.debug(
291 log.debug(
287 "Using the following Mako template directories: %s",
292 "Using the following Mako template directories: %s",
288 mako_directories)
293 mako_directories)
289
294
290 # Default includes, possible to change as a user
295 # Default includes, possible to change as a user
291 pyramid_includes = settings.setdefault('pyramid.includes', [
296 pyramid_includes = settings.setdefault('pyramid.includes', [
292 'rhodecode.lib.middleware.request_wrapper',
297 'rhodecode.lib.middleware.request_wrapper',
293 ])
298 ])
294 log.debug(
299 log.debug(
295 "Using the following pyramid.includes: %s",
300 "Using the following pyramid.includes: %s",
296 pyramid_includes)
301 pyramid_includes)
297
302
298 # TODO: johbo: Re-think this, usually the call to config.include
303 # TODO: johbo: Re-think this, usually the call to config.include
299 # should allow to pass in a prefix.
304 # should allow to pass in a prefix.
300 settings.setdefault('rhodecode.api.url', '/_admin/api')
305 settings.setdefault('rhodecode.api.url', '/_admin/api')
301
306
302 _bool_setting(settings, 'vcs.server.enable', 'true')
307 _bool_setting(settings, 'vcs.server.enable', 'true')
303 _bool_setting(settings, 'static_files', 'true')
308 _bool_setting(settings, 'static_files', 'true')
309 _bool_setting(settings, 'is_test', 'false')
304
310
305 return settings
311 return settings
306
312
307
313
308 def _bool_setting(settings, name, default):
314 def _bool_setting(settings, name, default):
309 settings[name] = asbool(settings.get(name, default))
315 settings[name] = asbool(settings.get(name, default))
@@ -1,1112 +1,1149 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 from routes import Mapper
33 from routes import Mapper
33
34
34 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
35
36
36 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
37 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
38
39
39 # Default requirements for URL parts
40 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
41 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
42 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
43 'group_name': r'.*?[^/]',
43 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
44 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
45 # file path eats up everything at the end
46 # file path eats up everything at the end
46 'f_path': r'.*',
47 'f_path': r'.*',
47 # reference types
48 # reference types
48 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 }
51 }
51
52
52
53
54 class JSRoutesMapper(Mapper):
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
63
64 def connect(self, *args, **kw):
65 """
66 Wrapper for connect to take an extra argument jsroute=True
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
77 def _extract_route_information(self, route):
78 """
79 Convert a route into tuple(name, path, args), eg:
80 ('user_profile', '/profile/%(username)s', ['username'])
81 """
82 routepath = route.routepath
83 def replace(matchobj):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
87 return "%%(%s)s" % matchobj.group(2)
88
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
91 route.name,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
96
97 def jsroutes(self):
98 """
99 Return a list of pyroutes.js compatible routes
100 """
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
103
104
53 def make_map(config):
105 def make_map(config):
54 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
55 rmap = Mapper(directory=config['pylons.paths']['controllers'],
107 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
56 always_scan=config['debug'])
108 always_scan=config['debug'])
57 rmap.minimization = False
109 rmap.minimization = False
58 rmap.explicit = False
110 rmap.explicit = False
59
111
60 from rhodecode.lib.utils2 import str2bool
112 from rhodecode.lib.utils2 import str2bool
61 from rhodecode.model import repo, repo_group
113 from rhodecode.model import repo, repo_group
62
114
63 def check_repo(environ, match_dict):
115 def check_repo(environ, match_dict):
64 """
116 """
65 check for valid repository for proper 404 handling
117 check for valid repository for proper 404 handling
66
118
67 :param environ:
119 :param environ:
68 :param match_dict:
120 :param match_dict:
69 """
121 """
70 repo_name = match_dict.get('repo_name')
122 repo_name = match_dict.get('repo_name')
71
123
72 if match_dict.get('f_path'):
124 if match_dict.get('f_path'):
73 # fix for multiple initial slashes that causes errors
125 # fix for multiple initial slashes that causes errors
74 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
126 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
75 repo_model = repo.RepoModel()
127 repo_model = repo.RepoModel()
76 by_name_match = repo_model.get_by_repo_name(repo_name)
128 by_name_match = repo_model.get_by_repo_name(repo_name)
77 # if we match quickly from database, short circuit the operation,
129 # if we match quickly from database, short circuit the operation,
78 # and validate repo based on the type.
130 # and validate repo based on the type.
79 if by_name_match:
131 if by_name_match:
80 return True
132 return True
81
133
82 by_id_match = repo_model.get_repo_by_id(repo_name)
134 by_id_match = repo_model.get_repo_by_id(repo_name)
83 if by_id_match:
135 if by_id_match:
84 repo_name = by_id_match.repo_name
136 repo_name = by_id_match.repo_name
85 match_dict['repo_name'] = repo_name
137 match_dict['repo_name'] = repo_name
86 return True
138 return True
87
139
88 return False
140 return False
89
141
90 def check_group(environ, match_dict):
142 def check_group(environ, match_dict):
91 """
143 """
92 check for valid repository group path for proper 404 handling
144 check for valid repository group path for proper 404 handling
93
145
94 :param environ:
146 :param environ:
95 :param match_dict:
147 :param match_dict:
96 """
148 """
97 repo_group_name = match_dict.get('group_name')
149 repo_group_name = match_dict.get('group_name')
98 repo_group_model = repo_group.RepoGroupModel()
150 repo_group_model = repo_group.RepoGroupModel()
99 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
151 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
100 if by_name_match:
152 if by_name_match:
101 return True
153 return True
102
154
103 return False
155 return False
104
156
105 def check_user_group(environ, match_dict):
157 def check_user_group(environ, match_dict):
106 """
158 """
107 check for valid user group for proper 404 handling
159 check for valid user group for proper 404 handling
108
160
109 :param environ:
161 :param environ:
110 :param match_dict:
162 :param match_dict:
111 """
163 """
112 return True
164 return True
113
165
114 def check_int(environ, match_dict):
166 def check_int(environ, match_dict):
115 return match_dict.get('id').isdigit()
167 return match_dict.get('id').isdigit()
116
168
117 # The ErrorController route (handles 404/500 error pages); it should
169 # The ErrorController route (handles 404/500 error pages); it should
118 # likely stay at the top, ensuring it can always be resolved
170 # likely stay at the top, ensuring it can always be resolved
119 rmap.connect('/error/{action}', controller='error')
171 rmap.connect('/error/{action}', controller='error')
120 rmap.connect('/error/{action}/{id}', controller='error')
172 rmap.connect('/error/{action}/{id}', controller='error')
121
173
122 #==========================================================================
174 #==========================================================================
123 # CUSTOM ROUTES HERE
175 # CUSTOM ROUTES HERE
124 #==========================================================================
176 #==========================================================================
125
177
126 # MAIN PAGE
178 # MAIN PAGE
127 rmap.connect('home', '/', controller='home', action='index')
179 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
128 rmap.connect('repo_switcher_data', '/_repos_and_groups', controller='home',
180 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
129 action='repo_switcher_data')
181 action='goto_switcher_data')
130 rmap.connect('repo_list_data', '/_repos', controller='home',
182 rmap.connect('repo_list_data', '/_repos', controller='home',
131 action='repo_list_data')
183 action='repo_list_data')
132
184
133 rmap.connect('user_autocomplete_data', '/_users', controller='home',
185 rmap.connect('user_autocomplete_data', '/_users', controller='home',
134 action='user_autocomplete_data')
186 action='user_autocomplete_data', jsroute=True)
135 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
187 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
136 action='user_group_autocomplete_data')
188 action='user_group_autocomplete_data')
137
189
138 rmap.connect(
190 rmap.connect(
139 'user_profile', '/_profiles/{username}', controller='users',
191 'user_profile', '/_profiles/{username}', controller='users',
140 action='user_profile')
192 action='user_profile')
141
193
142 # TODO: johbo: Static links, to be replaced by our redirection mechanism
194 # TODO: johbo: Static links, to be replaced by our redirection mechanism
143 rmap.connect('rst_help',
195 rmap.connect('rst_help',
144 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
196 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
145 _static=True)
197 _static=True)
146 rmap.connect('markdown_help',
198 rmap.connect('markdown_help',
147 'http://daringfireball.net/projects/markdown/syntax',
199 'http://daringfireball.net/projects/markdown/syntax',
148 _static=True)
200 _static=True)
149 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
201 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
150 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
202 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
151 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
203 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
152 # TODO: anderson - making this a static link since redirect won't play
204 # TODO: anderson - making this a static link since redirect won't play
153 # nice with POST requests
205 # nice with POST requests
154 rmap.connect('enterprise_license_convert_from_old',
206 rmap.connect('enterprise_license_convert_from_old',
155 'https://rhodecode.com/u/license-upgrade',
207 'https://rhodecode.com/u/license-upgrade',
156 _static=True)
208 _static=True)
157
209
158 routing_links.connect_redirection_links(rmap)
210 routing_links.connect_redirection_links(rmap)
159
211
160 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
212 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
161 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
213 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
162
214
163 # ADMIN REPOSITORY ROUTES
215 # ADMIN REPOSITORY ROUTES
164 with rmap.submapper(path_prefix=ADMIN_PREFIX,
216 with rmap.submapper(path_prefix=ADMIN_PREFIX,
165 controller='admin/repos') as m:
217 controller='admin/repos') as m:
166 m.connect('repos', '/repos',
218 m.connect('repos', '/repos',
167 action='create', conditions={'method': ['POST']})
219 action='create', conditions={'method': ['POST']})
168 m.connect('repos', '/repos',
220 m.connect('repos', '/repos',
169 action='index', conditions={'method': ['GET']})
221 action='index', conditions={'method': ['GET']})
170 m.connect('new_repo', '/create_repository',
222 m.connect('new_repo', '/create_repository', jsroute=True,
171 action='create_repository', conditions={'method': ['GET']})
223 action='create_repository', conditions={'method': ['GET']})
172 m.connect('/repos/{repo_name}',
224 m.connect('/repos/{repo_name}',
173 action='update', conditions={'method': ['PUT'],
225 action='update', conditions={'method': ['PUT'],
174 'function': check_repo},
226 'function': check_repo},
175 requirements=URL_NAME_REQUIREMENTS)
227 requirements=URL_NAME_REQUIREMENTS)
176 m.connect('delete_repo', '/repos/{repo_name}',
228 m.connect('delete_repo', '/repos/{repo_name}',
177 action='delete', conditions={'method': ['DELETE']},
229 action='delete', conditions={'method': ['DELETE']},
178 requirements=URL_NAME_REQUIREMENTS)
230 requirements=URL_NAME_REQUIREMENTS)
179 m.connect('repo', '/repos/{repo_name}',
231 m.connect('repo', '/repos/{repo_name}',
180 action='show', conditions={'method': ['GET'],
232 action='show', conditions={'method': ['GET'],
181 'function': check_repo},
233 'function': check_repo},
182 requirements=URL_NAME_REQUIREMENTS)
234 requirements=URL_NAME_REQUIREMENTS)
183
235
184 # ADMIN REPOSITORY GROUPS ROUTES
236 # ADMIN REPOSITORY GROUPS ROUTES
185 with rmap.submapper(path_prefix=ADMIN_PREFIX,
237 with rmap.submapper(path_prefix=ADMIN_PREFIX,
186 controller='admin/repo_groups') as m:
238 controller='admin/repo_groups') as m:
187 m.connect('repo_groups', '/repo_groups',
239 m.connect('repo_groups', '/repo_groups',
188 action='create', conditions={'method': ['POST']})
240 action='create', conditions={'method': ['POST']})
189 m.connect('repo_groups', '/repo_groups',
241 m.connect('repo_groups', '/repo_groups',
190 action='index', conditions={'method': ['GET']})
242 action='index', conditions={'method': ['GET']})
191 m.connect('new_repo_group', '/repo_groups/new',
243 m.connect('new_repo_group', '/repo_groups/new',
192 action='new', conditions={'method': ['GET']})
244 action='new', conditions={'method': ['GET']})
193 m.connect('update_repo_group', '/repo_groups/{group_name}',
245 m.connect('update_repo_group', '/repo_groups/{group_name}',
194 action='update', conditions={'method': ['PUT'],
246 action='update', conditions={'method': ['PUT'],
195 'function': check_group},
247 'function': check_group},
196 requirements=URL_NAME_REQUIREMENTS)
248 requirements=URL_NAME_REQUIREMENTS)
197
249
198 # EXTRAS REPO GROUP ROUTES
250 # EXTRAS REPO GROUP ROUTES
199 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
251 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
200 action='edit',
252 action='edit',
201 conditions={'method': ['GET'], 'function': check_group},
253 conditions={'method': ['GET'], 'function': check_group},
202 requirements=URL_NAME_REQUIREMENTS)
254 requirements=URL_NAME_REQUIREMENTS)
203 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
255 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
204 action='edit',
256 action='edit',
205 conditions={'method': ['PUT'], 'function': check_group},
257 conditions={'method': ['PUT'], 'function': check_group},
206 requirements=URL_NAME_REQUIREMENTS)
258 requirements=URL_NAME_REQUIREMENTS)
207
259
208 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
260 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
209 action='edit_repo_group_advanced',
261 action='edit_repo_group_advanced',
210 conditions={'method': ['GET'], 'function': check_group},
262 conditions={'method': ['GET'], 'function': check_group},
211 requirements=URL_NAME_REQUIREMENTS)
263 requirements=URL_NAME_REQUIREMENTS)
212 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
264 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
213 action='edit_repo_group_advanced',
265 action='edit_repo_group_advanced',
214 conditions={'method': ['PUT'], 'function': check_group},
266 conditions={'method': ['PUT'], 'function': check_group},
215 requirements=URL_NAME_REQUIREMENTS)
267 requirements=URL_NAME_REQUIREMENTS)
216
268
217 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
269 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
218 action='edit_repo_group_perms',
270 action='edit_repo_group_perms',
219 conditions={'method': ['GET'], 'function': check_group},
271 conditions={'method': ['GET'], 'function': check_group},
220 requirements=URL_NAME_REQUIREMENTS)
272 requirements=URL_NAME_REQUIREMENTS)
221 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
273 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
222 action='update_perms',
274 action='update_perms',
223 conditions={'method': ['PUT'], 'function': check_group},
275 conditions={'method': ['PUT'], 'function': check_group},
224 requirements=URL_NAME_REQUIREMENTS)
276 requirements=URL_NAME_REQUIREMENTS)
225
277
226 m.connect('delete_repo_group', '/repo_groups/{group_name}',
278 m.connect('delete_repo_group', '/repo_groups/{group_name}',
227 action='delete', conditions={'method': ['DELETE'],
279 action='delete', conditions={'method': ['DELETE'],
228 'function': check_group},
280 'function': check_group},
229 requirements=URL_NAME_REQUIREMENTS)
281 requirements=URL_NAME_REQUIREMENTS)
230
282
231 # ADMIN USER ROUTES
283 # ADMIN USER ROUTES
232 with rmap.submapper(path_prefix=ADMIN_PREFIX,
284 with rmap.submapper(path_prefix=ADMIN_PREFIX,
233 controller='admin/users') as m:
285 controller='admin/users') as m:
234 m.connect('users', '/users',
286 m.connect('users', '/users',
235 action='create', conditions={'method': ['POST']})
287 action='create', conditions={'method': ['POST']})
236 m.connect('users', '/users',
288 m.connect('users', '/users',
237 action='index', conditions={'method': ['GET']})
289 action='index', conditions={'method': ['GET']})
238 m.connect('new_user', '/users/new',
290 m.connect('new_user', '/users/new',
239 action='new', conditions={'method': ['GET']})
291 action='new', conditions={'method': ['GET']})
240 m.connect('update_user', '/users/{user_id}',
292 m.connect('update_user', '/users/{user_id}',
241 action='update', conditions={'method': ['PUT']})
293 action='update', conditions={'method': ['PUT']})
242 m.connect('delete_user', '/users/{user_id}',
294 m.connect('delete_user', '/users/{user_id}',
243 action='delete', conditions={'method': ['DELETE']})
295 action='delete', conditions={'method': ['DELETE']})
244 m.connect('edit_user', '/users/{user_id}/edit',
296 m.connect('edit_user', '/users/{user_id}/edit',
245 action='edit', conditions={'method': ['GET']})
297 action='edit', conditions={'method': ['GET']})
246 m.connect('user', '/users/{user_id}',
298 m.connect('user', '/users/{user_id}',
247 action='show', conditions={'method': ['GET']})
299 action='show', conditions={'method': ['GET']})
248 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
300 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
249 action='reset_password', conditions={'method': ['POST']})
301 action='reset_password', conditions={'method': ['POST']})
250 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
302 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
251 action='create_personal_repo_group', conditions={'method': ['POST']})
303 action='create_personal_repo_group', conditions={'method': ['POST']})
252
304
253 # EXTRAS USER ROUTES
305 # EXTRAS USER ROUTES
254 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
306 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
255 action='edit_advanced', conditions={'method': ['GET']})
307 action='edit_advanced', conditions={'method': ['GET']})
256 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
308 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
257 action='update_advanced', conditions={'method': ['PUT']})
309 action='update_advanced', conditions={'method': ['PUT']})
258
310
259 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
311 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
260 action='edit_auth_tokens', conditions={'method': ['GET']})
312 action='edit_auth_tokens', conditions={'method': ['GET']})
261 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
313 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
262 action='add_auth_token', conditions={'method': ['PUT']})
314 action='add_auth_token', conditions={'method': ['PUT']})
263 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
315 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
264 action='delete_auth_token', conditions={'method': ['DELETE']})
316 action='delete_auth_token', conditions={'method': ['DELETE']})
265
317
266 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
267 action='edit_global_perms', conditions={'method': ['GET']})
319 action='edit_global_perms', conditions={'method': ['GET']})
268 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
320 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 action='update_global_perms', conditions={'method': ['PUT']})
321 action='update_global_perms', conditions={'method': ['PUT']})
270
322
271 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
323 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
272 action='edit_perms_summary', conditions={'method': ['GET']})
324 action='edit_perms_summary', conditions={'method': ['GET']})
273
325
274 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
275 action='edit_emails', conditions={'method': ['GET']})
327 action='edit_emails', conditions={'method': ['GET']})
276 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
328 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
277 action='add_email', conditions={'method': ['PUT']})
329 action='add_email', conditions={'method': ['PUT']})
278 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
330 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
279 action='delete_email', conditions={'method': ['DELETE']})
331 action='delete_email', conditions={'method': ['DELETE']})
280
332
281 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
282 action='edit_ips', conditions={'method': ['GET']})
334 action='edit_ips', conditions={'method': ['GET']})
283 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
335 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
284 action='add_ip', conditions={'method': ['PUT']})
336 action='add_ip', conditions={'method': ['PUT']})
285 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
337 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
286 action='delete_ip', conditions={'method': ['DELETE']})
338 action='delete_ip', conditions={'method': ['DELETE']})
287
339
288 # ADMIN USER GROUPS REST ROUTES
340 # ADMIN USER GROUPS REST ROUTES
289 with rmap.submapper(path_prefix=ADMIN_PREFIX,
341 with rmap.submapper(path_prefix=ADMIN_PREFIX,
290 controller='admin/user_groups') as m:
342 controller='admin/user_groups') as m:
291 m.connect('users_groups', '/user_groups',
343 m.connect('users_groups', '/user_groups',
292 action='create', conditions={'method': ['POST']})
344 action='create', conditions={'method': ['POST']})
293 m.connect('users_groups', '/user_groups',
345 m.connect('users_groups', '/user_groups',
294 action='index', conditions={'method': ['GET']})
346 action='index', conditions={'method': ['GET']})
295 m.connect('new_users_group', '/user_groups/new',
347 m.connect('new_users_group', '/user_groups/new',
296 action='new', conditions={'method': ['GET']})
348 action='new', conditions={'method': ['GET']})
297 m.connect('update_users_group', '/user_groups/{user_group_id}',
349 m.connect('update_users_group', '/user_groups/{user_group_id}',
298 action='update', conditions={'method': ['PUT']})
350 action='update', conditions={'method': ['PUT']})
299 m.connect('delete_users_group', '/user_groups/{user_group_id}',
351 m.connect('delete_users_group', '/user_groups/{user_group_id}',
300 action='delete', conditions={'method': ['DELETE']})
352 action='delete', conditions={'method': ['DELETE']})
301 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
353 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
302 action='edit', conditions={'method': ['GET']},
354 action='edit', conditions={'method': ['GET']},
303 function=check_user_group)
355 function=check_user_group)
304
356
305 # EXTRAS USER GROUP ROUTES
357 # EXTRAS USER GROUP ROUTES
306 m.connect('edit_user_group_global_perms', '/user_groups/{user_group_id}/edit/global_permissions',
358 m.connect('edit_user_group_global_perms',
359 '/user_groups/{user_group_id}/edit/global_permissions',
307 action='edit_global_perms', conditions={'method': ['GET']})
360 action='edit_global_perms', conditions={'method': ['GET']})
308 m.connect('edit_user_group_global_perms', '/user_groups/{user_group_id}/edit/global_permissions',
361 m.connect('edit_user_group_global_perms',
362 '/user_groups/{user_group_id}/edit/global_permissions',
309 action='update_global_perms', conditions={'method': ['PUT']})
363 action='update_global_perms', conditions={'method': ['PUT']})
310 m.connect('edit_user_group_perms_summary', '/user_groups/{user_group_id}/edit/permissions_summary',
364 m.connect('edit_user_group_perms_summary',
365 '/user_groups/{user_group_id}/edit/permissions_summary',
311 action='edit_perms_summary', conditions={'method': ['GET']})
366 action='edit_perms_summary', conditions={'method': ['GET']})
312
367
313 m.connect('edit_user_group_perms', '/user_groups/{user_group_id}/edit/permissions',
368 m.connect('edit_user_group_perms',
369 '/user_groups/{user_group_id}/edit/permissions',
314 action='edit_perms', conditions={'method': ['GET']})
370 action='edit_perms', conditions={'method': ['GET']})
315 m.connect('edit_user_group_perms', '/user_groups/{user_group_id}/edit/permissions',
371 m.connect('edit_user_group_perms',
372 '/user_groups/{user_group_id}/edit/permissions',
316 action='update_perms', conditions={'method': ['PUT']})
373 action='update_perms', conditions={'method': ['PUT']})
317
374
318 m.connect('edit_user_group_advanced', '/user_groups/{user_group_id}/edit/advanced',
375 m.connect('edit_user_group_advanced',
376 '/user_groups/{user_group_id}/edit/advanced',
319 action='edit_advanced', conditions={'method': ['GET']})
377 action='edit_advanced', conditions={'method': ['GET']})
320
378
321 m.connect('edit_user_group_members', '/user_groups/{user_group_id}/edit/members',
379 m.connect('edit_user_group_members',
380 '/user_groups/{user_group_id}/edit/members', jsroute=True,
322 action='edit_members', conditions={'method': ['GET']})
381 action='edit_members', conditions={'method': ['GET']})
323
382
324 # ADMIN PERMISSIONS ROUTES
383 # ADMIN PERMISSIONS ROUTES
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 controller='admin/permissions') as m:
385 controller='admin/permissions') as m:
327 m.connect('admin_permissions_application', '/permissions/application',
386 m.connect('admin_permissions_application', '/permissions/application',
328 action='permission_application_update', conditions={'method': ['POST']})
387 action='permission_application_update', conditions={'method': ['POST']})
329 m.connect('admin_permissions_application', '/permissions/application',
388 m.connect('admin_permissions_application', '/permissions/application',
330 action='permission_application', conditions={'method': ['GET']})
389 action='permission_application', conditions={'method': ['GET']})
331
390
332 m.connect('admin_permissions_global', '/permissions/global',
391 m.connect('admin_permissions_global', '/permissions/global',
333 action='permission_global_update', conditions={'method': ['POST']})
392 action='permission_global_update', conditions={'method': ['POST']})
334 m.connect('admin_permissions_global', '/permissions/global',
393 m.connect('admin_permissions_global', '/permissions/global',
335 action='permission_global', conditions={'method': ['GET']})
394 action='permission_global', conditions={'method': ['GET']})
336
395
337 m.connect('admin_permissions_object', '/permissions/object',
396 m.connect('admin_permissions_object', '/permissions/object',
338 action='permission_objects_update', conditions={'method': ['POST']})
397 action='permission_objects_update', conditions={'method': ['POST']})
339 m.connect('admin_permissions_object', '/permissions/object',
398 m.connect('admin_permissions_object', '/permissions/object',
340 action='permission_objects', conditions={'method': ['GET']})
399 action='permission_objects', conditions={'method': ['GET']})
341
400
342 m.connect('admin_permissions_ips', '/permissions/ips',
401 m.connect('admin_permissions_ips', '/permissions/ips',
343 action='permission_ips', conditions={'method': ['POST']})
402 action='permission_ips', conditions={'method': ['POST']})
344 m.connect('admin_permissions_ips', '/permissions/ips',
403 m.connect('admin_permissions_ips', '/permissions/ips',
345 action='permission_ips', conditions={'method': ['GET']})
404 action='permission_ips', conditions={'method': ['GET']})
346
405
347 m.connect('admin_permissions_overview', '/permissions/overview',
406 m.connect('admin_permissions_overview', '/permissions/overview',
348 action='permission_perms', conditions={'method': ['GET']})
407 action='permission_perms', conditions={'method': ['GET']})
349
408
350 # ADMIN DEFAULTS REST ROUTES
409 # ADMIN DEFAULTS REST ROUTES
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
410 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 controller='admin/defaults') as m:
411 controller='admin/defaults') as m:
353 m.connect('admin_defaults_repositories', '/defaults/repositories',
412 m.connect('admin_defaults_repositories', '/defaults/repositories',
354 action='update_repository_defaults', conditions={'method': ['POST']})
413 action='update_repository_defaults', conditions={'method': ['POST']})
355 m.connect('admin_defaults_repositories', '/defaults/repositories',
414 m.connect('admin_defaults_repositories', '/defaults/repositories',
356 action='index', conditions={'method': ['GET']})
415 action='index', conditions={'method': ['GET']})
357
416
358 # ADMIN DEBUG STYLE ROUTES
417 # ADMIN DEBUG STYLE ROUTES
359 if str2bool(config.get('debug_style')):
418 if str2bool(config.get('debug_style')):
360 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
419 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
361 controller='debug_style') as m:
420 controller='debug_style') as m:
362 m.connect('debug_style_home', '',
421 m.connect('debug_style_home', '',
363 action='index', conditions={'method': ['GET']})
422 action='index', conditions={'method': ['GET']})
364 m.connect('debug_style_template', '/t/{t_path}',
423 m.connect('debug_style_template', '/t/{t_path}',
365 action='template', conditions={'method': ['GET']})
424 action='template', conditions={'method': ['GET']})
366
425
367 # ADMIN SETTINGS ROUTES
426 # ADMIN SETTINGS ROUTES
368 with rmap.submapper(path_prefix=ADMIN_PREFIX,
427 with rmap.submapper(path_prefix=ADMIN_PREFIX,
369 controller='admin/settings') as m:
428 controller='admin/settings') as m:
370
429
371 # default
430 # default
372 m.connect('admin_settings', '/settings',
431 m.connect('admin_settings', '/settings',
373 action='settings_global_update',
432 action='settings_global_update',
374 conditions={'method': ['POST']})
433 conditions={'method': ['POST']})
375 m.connect('admin_settings', '/settings',
434 m.connect('admin_settings', '/settings',
376 action='settings_global', conditions={'method': ['GET']})
435 action='settings_global', conditions={'method': ['GET']})
377
436
378 m.connect('admin_settings_vcs', '/settings/vcs',
437 m.connect('admin_settings_vcs', '/settings/vcs',
379 action='settings_vcs_update',
438 action='settings_vcs_update',
380 conditions={'method': ['POST']})
439 conditions={'method': ['POST']})
381 m.connect('admin_settings_vcs', '/settings/vcs',
440 m.connect('admin_settings_vcs', '/settings/vcs',
382 action='settings_vcs',
441 action='settings_vcs',
383 conditions={'method': ['GET']})
442 conditions={'method': ['GET']})
384 m.connect('admin_settings_vcs', '/settings/vcs',
443 m.connect('admin_settings_vcs', '/settings/vcs',
385 action='delete_svn_pattern',
444 action='delete_svn_pattern',
386 conditions={'method': ['DELETE']})
445 conditions={'method': ['DELETE']})
387
446
388 m.connect('admin_settings_mapping', '/settings/mapping',
447 m.connect('admin_settings_mapping', '/settings/mapping',
389 action='settings_mapping_update',
448 action='settings_mapping_update',
390 conditions={'method': ['POST']})
449 conditions={'method': ['POST']})
391 m.connect('admin_settings_mapping', '/settings/mapping',
450 m.connect('admin_settings_mapping', '/settings/mapping',
392 action='settings_mapping', conditions={'method': ['GET']})
451 action='settings_mapping', conditions={'method': ['GET']})
393
452
394 m.connect('admin_settings_global', '/settings/global',
453 m.connect('admin_settings_global', '/settings/global',
395 action='settings_global_update',
454 action='settings_global_update',
396 conditions={'method': ['POST']})
455 conditions={'method': ['POST']})
397 m.connect('admin_settings_global', '/settings/global',
456 m.connect('admin_settings_global', '/settings/global',
398 action='settings_global', conditions={'method': ['GET']})
457 action='settings_global', conditions={'method': ['GET']})
399
458
400 m.connect('admin_settings_visual', '/settings/visual',
459 m.connect('admin_settings_visual', '/settings/visual',
401 action='settings_visual_update',
460 action='settings_visual_update',
402 conditions={'method': ['POST']})
461 conditions={'method': ['POST']})
403 m.connect('admin_settings_visual', '/settings/visual',
462 m.connect('admin_settings_visual', '/settings/visual',
404 action='settings_visual', conditions={'method': ['GET']})
463 action='settings_visual', conditions={'method': ['GET']})
405
464
406 m.connect('admin_settings_issuetracker',
465 m.connect('admin_settings_issuetracker',
407 '/settings/issue-tracker', action='settings_issuetracker',
466 '/settings/issue-tracker', action='settings_issuetracker',
408 conditions={'method': ['GET']})
467 conditions={'method': ['GET']})
409 m.connect('admin_settings_issuetracker_save',
468 m.connect('admin_settings_issuetracker_save',
410 '/settings/issue-tracker/save',
469 '/settings/issue-tracker/save',
411 action='settings_issuetracker_save',
470 action='settings_issuetracker_save',
412 conditions={'method': ['POST']})
471 conditions={'method': ['POST']})
413 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
472 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
414 action='settings_issuetracker_test',
473 action='settings_issuetracker_test',
415 conditions={'method': ['POST']})
474 conditions={'method': ['POST']})
416 m.connect('admin_issuetracker_delete',
475 m.connect('admin_issuetracker_delete',
417 '/settings/issue-tracker/delete',
476 '/settings/issue-tracker/delete',
418 action='settings_issuetracker_delete',
477 action='settings_issuetracker_delete',
419 conditions={'method': ['DELETE']})
478 conditions={'method': ['DELETE']})
420
479
421 m.connect('admin_settings_email', '/settings/email',
480 m.connect('admin_settings_email', '/settings/email',
422 action='settings_email_update',
481 action='settings_email_update',
423 conditions={'method': ['POST']})
482 conditions={'method': ['POST']})
424 m.connect('admin_settings_email', '/settings/email',
483 m.connect('admin_settings_email', '/settings/email',
425 action='settings_email', conditions={'method': ['GET']})
484 action='settings_email', conditions={'method': ['GET']})
426
485
427 m.connect('admin_settings_hooks', '/settings/hooks',
486 m.connect('admin_settings_hooks', '/settings/hooks',
428 action='settings_hooks_update',
487 action='settings_hooks_update',
429 conditions={'method': ['POST', 'DELETE']})
488 conditions={'method': ['POST', 'DELETE']})
430 m.connect('admin_settings_hooks', '/settings/hooks',
489 m.connect('admin_settings_hooks', '/settings/hooks',
431 action='settings_hooks', conditions={'method': ['GET']})
490 action='settings_hooks', conditions={'method': ['GET']})
432
491
433 m.connect('admin_settings_search', '/settings/search',
492 m.connect('admin_settings_search', '/settings/search',
434 action='settings_search', conditions={'method': ['GET']})
493 action='settings_search', conditions={'method': ['GET']})
435
494
436 m.connect('admin_settings_system', '/settings/system',
495 m.connect('admin_settings_system', '/settings/system',
437 action='settings_system', conditions={'method': ['GET']})
496 action='settings_system', conditions={'method': ['GET']})
438
497
439 m.connect('admin_settings_system_update', '/settings/system/updates',
498 m.connect('admin_settings_system_update', '/settings/system/updates',
440 action='settings_system_update', conditions={'method': ['GET']})
499 action='settings_system_update', conditions={'method': ['GET']})
441
500
442 m.connect('admin_settings_supervisor', '/settings/supervisor',
501 m.connect('admin_settings_supervisor', '/settings/supervisor',
443 action='settings_supervisor', conditions={'method': ['GET']})
502 action='settings_supervisor', conditions={'method': ['GET']})
444 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
503 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
445 action='settings_supervisor_log', conditions={'method': ['GET']})
504 action='settings_supervisor_log', conditions={'method': ['GET']})
446
505
447 m.connect('admin_settings_labs', '/settings/labs',
506 m.connect('admin_settings_labs', '/settings/labs',
448 action='settings_labs_update',
507 action='settings_labs_update',
449 conditions={'method': ['POST']})
508 conditions={'method': ['POST']})
450 m.connect('admin_settings_labs', '/settings/labs',
509 m.connect('admin_settings_labs', '/settings/labs',
451 action='settings_labs', conditions={'method': ['GET']})
510 action='settings_labs', conditions={'method': ['GET']})
452
511
453 m.connect('admin_settings_open_source', '/settings/open_source',
512 m.connect('admin_settings_open_source', '/settings/open_source',
454 action='settings_open_source',
513 action='settings_open_source',
455 conditions={'method': ['GET']})
514 conditions={'method': ['GET']})
456
515
457 # ADMIN MY ACCOUNT
516 # ADMIN MY ACCOUNT
458 with rmap.submapper(path_prefix=ADMIN_PREFIX,
517 with rmap.submapper(path_prefix=ADMIN_PREFIX,
459 controller='admin/my_account') as m:
518 controller='admin/my_account') as m:
460
519
461 m.connect('my_account', '/my_account',
520 m.connect('my_account', '/my_account',
462 action='my_account', conditions={'method': ['GET']})
521 action='my_account', conditions={'method': ['GET']})
463 m.connect('my_account_edit', '/my_account/edit',
522 m.connect('my_account_edit', '/my_account/edit',
464 action='my_account_edit', conditions={'method': ['GET']})
523 action='my_account_edit', conditions={'method': ['GET']})
465 m.connect('my_account', '/my_account',
524 m.connect('my_account', '/my_account',
466 action='my_account_update', conditions={'method': ['POST']})
525 action='my_account_update', conditions={'method': ['POST']})
467
526
468 m.connect('my_account_password', '/my_account/password',
527 m.connect('my_account_password', '/my_account/password',
469 action='my_account_password', conditions={'method': ['GET']})
528 action='my_account_password', conditions={'method': ['GET']})
470 m.connect('my_account_password', '/my_account/password',
529 m.connect('my_account_password', '/my_account/password',
471 action='my_account_password_update', conditions={'method': ['POST']})
530 action='my_account_password_update', conditions={'method': ['POST']})
472
531
473 m.connect('my_account_repos', '/my_account/repos',
532 m.connect('my_account_repos', '/my_account/repos',
474 action='my_account_repos', conditions={'method': ['GET']})
533 action='my_account_repos', conditions={'method': ['GET']})
475
534
476 m.connect('my_account_watched', '/my_account/watched',
535 m.connect('my_account_watched', '/my_account/watched',
477 action='my_account_watched', conditions={'method': ['GET']})
536 action='my_account_watched', conditions={'method': ['GET']})
478
537
479 m.connect('my_account_pullrequests', '/my_account/pull_requests',
538 m.connect('my_account_pullrequests', '/my_account/pull_requests',
480 action='my_account_pullrequests', conditions={'method': ['GET']})
539 action='my_account_pullrequests', conditions={'method': ['GET']})
481
540
482 m.connect('my_account_perms', '/my_account/perms',
541 m.connect('my_account_perms', '/my_account/perms',
483 action='my_account_perms', conditions={'method': ['GET']})
542 action='my_account_perms', conditions={'method': ['GET']})
484
543
485 m.connect('my_account_emails', '/my_account/emails',
544 m.connect('my_account_emails', '/my_account/emails',
486 action='my_account_emails', conditions={'method': ['GET']})
545 action='my_account_emails', conditions={'method': ['GET']})
487 m.connect('my_account_emails', '/my_account/emails',
546 m.connect('my_account_emails', '/my_account/emails',
488 action='my_account_emails_add', conditions={'method': ['POST']})
547 action='my_account_emails_add', conditions={'method': ['POST']})
489 m.connect('my_account_emails', '/my_account/emails',
548 m.connect('my_account_emails', '/my_account/emails',
490 action='my_account_emails_delete', conditions={'method': ['DELETE']})
549 action='my_account_emails_delete', conditions={'method': ['DELETE']})
491
550
492 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
551 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
493 action='my_account_auth_tokens', conditions={'method': ['GET']})
552 action='my_account_auth_tokens', conditions={'method': ['GET']})
494 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
553 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
495 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
554 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
496 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
555 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
497 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
556 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
498
557
499 m.connect('my_account_oauth', '/my_account/oauth',
500 action='my_account_oauth', conditions={'method': ['GET']})
501 m.connect('my_account_oauth', '/my_account/oauth',
502 action='my_account_oauth_delete',
503 conditions={'method': ['DELETE']})
504
505 # NOTIFICATION REST ROUTES
558 # NOTIFICATION REST ROUTES
506 with rmap.submapper(path_prefix=ADMIN_PREFIX,
559 with rmap.submapper(path_prefix=ADMIN_PREFIX,
507 controller='admin/notifications') as m:
560 controller='admin/notifications') as m:
508 m.connect('notifications', '/notifications',
561 m.connect('notifications', '/notifications',
509 action='index', conditions={'method': ['GET']})
562 action='index', conditions={'method': ['GET']})
510 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
563 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
511 action='mark_all_read', conditions={'method': ['POST']})
564 action='mark_all_read', conditions={'method': ['POST']})
512
565
513 m.connect('/notifications/{notification_id}',
566 m.connect('/notifications/{notification_id}',
514 action='update', conditions={'method': ['PUT']})
567 action='update', conditions={'method': ['PUT']})
515 m.connect('/notifications/{notification_id}',
568 m.connect('/notifications/{notification_id}',
516 action='delete', conditions={'method': ['DELETE']})
569 action='delete', conditions={'method': ['DELETE']})
517 m.connect('notification', '/notifications/{notification_id}',
570 m.connect('notification', '/notifications/{notification_id}',
518 action='show', conditions={'method': ['GET']})
571 action='show', conditions={'method': ['GET']})
519
572
520 # ADMIN GIST
573 # ADMIN GIST
521 with rmap.submapper(path_prefix=ADMIN_PREFIX,
574 with rmap.submapper(path_prefix=ADMIN_PREFIX,
522 controller='admin/gists') as m:
575 controller='admin/gists') as m:
523 m.connect('gists', '/gists',
576 m.connect('gists', '/gists',
524 action='create', conditions={'method': ['POST']})
577 action='create', conditions={'method': ['POST']})
525 m.connect('gists', '/gists',
578 m.connect('gists', '/gists', jsroute=True,
526 action='index', conditions={'method': ['GET']})
579 action='index', conditions={'method': ['GET']})
527 m.connect('new_gist', '/gists/new',
580 m.connect('new_gist', '/gists/new', jsroute=True,
528 action='new', conditions={'method': ['GET']})
581 action='new', conditions={'method': ['GET']})
529
582
530 m.connect('/gists/{gist_id}',
583 m.connect('/gists/{gist_id}',
531 action='delete', conditions={'method': ['DELETE']})
584 action='delete', conditions={'method': ['DELETE']})
532 m.connect('edit_gist', '/gists/{gist_id}/edit',
585 m.connect('edit_gist', '/gists/{gist_id}/edit',
533 action='edit_form', conditions={'method': ['GET']})
586 action='edit_form', conditions={'method': ['GET']})
534 m.connect('edit_gist', '/gists/{gist_id}/edit',
587 m.connect('edit_gist', '/gists/{gist_id}/edit',
535 action='edit', conditions={'method': ['POST']})
588 action='edit', conditions={'method': ['POST']})
536 m.connect(
589 m.connect(
537 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
590 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
538 action='check_revision', conditions={'method': ['GET']})
591 action='check_revision', conditions={'method': ['GET']})
539
592
540 m.connect('gist', '/gists/{gist_id}',
593 m.connect('gist', '/gists/{gist_id}',
541 action='show', conditions={'method': ['GET']})
594 action='show', conditions={'method': ['GET']})
542 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
595 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
543 revision='tip',
596 revision='tip',
544 action='show', conditions={'method': ['GET']})
597 action='show', conditions={'method': ['GET']})
545 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
598 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
546 revision='tip',
599 revision='tip',
547 action='show', conditions={'method': ['GET']})
600 action='show', conditions={'method': ['GET']})
548 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
601 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
549 revision='tip',
602 revision='tip',
550 action='show', conditions={'method': ['GET']},
603 action='show', conditions={'method': ['GET']},
551 requirements=URL_NAME_REQUIREMENTS)
604 requirements=URL_NAME_REQUIREMENTS)
552
605
553 # ADMIN MAIN PAGES
606 # ADMIN MAIN PAGES
554 with rmap.submapper(path_prefix=ADMIN_PREFIX,
607 with rmap.submapper(path_prefix=ADMIN_PREFIX,
555 controller='admin/admin') as m:
608 controller='admin/admin') as m:
556 m.connect('admin_home', '', action='index')
609 m.connect('admin_home', '', action='index')
557 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
610 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
558 action='add_repo')
611 action='add_repo')
559 m.connect(
612 m.connect(
560 'pull_requests_global', '/pull_requests/{pull_request_id:[0-9]+}',
613 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
561 action='pull_requests')
614 action='pull_requests')
615 m.connect(
616 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
617 action='pull_requests')
618
562
619
563 # USER JOURNAL
620 # USER JOURNAL
564 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
621 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
565 controller='journal', action='index')
622 controller='journal', action='index')
566 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
623 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
567 controller='journal', action='journal_rss')
624 controller='journal', action='journal_rss')
568 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
625 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
569 controller='journal', action='journal_atom')
626 controller='journal', action='journal_atom')
570
627
571 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
628 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
572 controller='journal', action='public_journal')
629 controller='journal', action='public_journal')
573
630
574 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
631 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
575 controller='journal', action='public_journal_rss')
632 controller='journal', action='public_journal_rss')
576
633
577 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
634 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
578 controller='journal', action='public_journal_rss')
635 controller='journal', action='public_journal_rss')
579
636
580 rmap.connect('public_journal_atom',
637 rmap.connect('public_journal_atom',
581 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
638 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
582 action='public_journal_atom')
639 action='public_journal_atom')
583
640
584 rmap.connect('public_journal_atom_old',
641 rmap.connect('public_journal_atom_old',
585 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
642 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
586 action='public_journal_atom')
643 action='public_journal_atom')
587
644
588 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
645 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
589 controller='journal', action='toggle_following',
646 controller='journal', action='toggle_following', jsroute=True,
590 conditions={'method': ['POST']})
647 conditions={'method': ['POST']})
591
648
592 # FULL TEXT SEARCH
649 # FULL TEXT SEARCH
593 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
650 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
594 controller='search')
651 controller='search')
595 rmap.connect('search_repo_home', '/{repo_name}/search',
652 rmap.connect('search_repo_home', '/{repo_name}/search',
596 controller='search',
653 controller='search',
597 action='index',
654 action='index',
598 conditions={'function': check_repo},
655 conditions={'function': check_repo},
599 requirements=URL_NAME_REQUIREMENTS)
656 requirements=URL_NAME_REQUIREMENTS)
600
657
601 # LOGIN/LOGOUT/REGISTER/SIGN IN
602 rmap.connect('login_home', '%s/login' % (ADMIN_PREFIX,), controller='login',
603 action='index')
604
605 rmap.connect('logout_home', '%s/logout' % (ADMIN_PREFIX,), controller='login',
606 action='logout', conditions={'method': ['POST']})
607
608 rmap.connect('register', '%s/register' % (ADMIN_PREFIX,), controller='login',
609 action='register')
610
611 rmap.connect('reset_password', '%s/password_reset' % (ADMIN_PREFIX,),
612 controller='login', action='password_reset')
613
614 rmap.connect('reset_password_confirmation',
615 '%s/password_reset_confirmation' % (ADMIN_PREFIX,),
616 controller='login', action='password_reset_confirmation')
617
618 rmap.connect('social_auth',
619 '%s/social_auth/{provider_name}' % (ADMIN_PREFIX,),
620 controller='login', action='social_auth')
621
622 # FEEDS
658 # FEEDS
623 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
659 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
624 controller='feed', action='rss',
660 controller='feed', action='rss',
625 conditions={'function': check_repo},
661 conditions={'function': check_repo},
626 requirements=URL_NAME_REQUIREMENTS)
662 requirements=URL_NAME_REQUIREMENTS)
627
663
628 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
664 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
629 controller='feed', action='atom',
665 controller='feed', action='atom',
630 conditions={'function': check_repo},
666 conditions={'function': check_repo},
631 requirements=URL_NAME_REQUIREMENTS)
667 requirements=URL_NAME_REQUIREMENTS)
632
668
633 #==========================================================================
669 #==========================================================================
634 # REPOSITORY ROUTES
670 # REPOSITORY ROUTES
635 #==========================================================================
671 #==========================================================================
636
672
637 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
673 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
638 controller='admin/repos', action='repo_creating',
674 controller='admin/repos', action='repo_creating',
639 requirements=URL_NAME_REQUIREMENTS)
675 requirements=URL_NAME_REQUIREMENTS)
640 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
676 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
641 controller='admin/repos', action='repo_check',
677 controller='admin/repos', action='repo_check',
642 requirements=URL_NAME_REQUIREMENTS)
678 requirements=URL_NAME_REQUIREMENTS)
643
679
644 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
680 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
645 controller='summary', action='repo_stats',
681 controller='summary', action='repo_stats',
646 conditions={'function': check_repo},
682 conditions={'function': check_repo},
647 requirements=URL_NAME_REQUIREMENTS)
683 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
648
684
649 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
685 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
650 controller='summary', action='repo_refs_data',
686 controller='summary', action='repo_refs_data', jsroute=True,
651 requirements=URL_NAME_REQUIREMENTS)
687 requirements=URL_NAME_REQUIREMENTS)
652 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
688 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
653 controller='summary', action='repo_refs_changelog_data',
689 controller='summary', action='repo_refs_changelog_data',
654 requirements=URL_NAME_REQUIREMENTS)
690 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
655
691
656 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
692 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
657 controller='changeset', revision='tip',
693 controller='changeset', revision='tip', jsroute=True,
658 conditions={'function': check_repo},
694 conditions={'function': check_repo},
659 requirements=URL_NAME_REQUIREMENTS)
695 requirements=URL_NAME_REQUIREMENTS)
660 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
696 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
661 controller='changeset', revision='tip', action='changeset_children',
697 controller='changeset', revision='tip', action='changeset_children',
662 conditions={'function': check_repo},
698 conditions={'function': check_repo},
663 requirements=URL_NAME_REQUIREMENTS)
699 requirements=URL_NAME_REQUIREMENTS)
664 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
700 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
665 controller='changeset', revision='tip', action='changeset_parents',
701 controller='changeset', revision='tip', action='changeset_parents',
666 conditions={'function': check_repo},
702 conditions={'function': check_repo},
667 requirements=URL_NAME_REQUIREMENTS)
703 requirements=URL_NAME_REQUIREMENTS)
668
704
669 # repo edit options
705 # repo edit options
670 rmap.connect('edit_repo', '/{repo_name}/settings',
706 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
671 controller='admin/repos', action='edit',
707 controller='admin/repos', action='edit',
672 conditions={'method': ['GET'], 'function': check_repo},
708 conditions={'method': ['GET'], 'function': check_repo},
673 requirements=URL_NAME_REQUIREMENTS)
709 requirements=URL_NAME_REQUIREMENTS)
674
710
675 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
711 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
712 jsroute=True,
676 controller='admin/repos', action='edit_permissions',
713 controller='admin/repos', action='edit_permissions',
677 conditions={'method': ['GET'], 'function': check_repo},
714 conditions={'method': ['GET'], 'function': check_repo},
678 requirements=URL_NAME_REQUIREMENTS)
715 requirements=URL_NAME_REQUIREMENTS)
679 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
716 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
680 controller='admin/repos', action='edit_permissions_update',
717 controller='admin/repos', action='edit_permissions_update',
681 conditions={'method': ['PUT'], 'function': check_repo},
718 conditions={'method': ['PUT'], 'function': check_repo},
682 requirements=URL_NAME_REQUIREMENTS)
719 requirements=URL_NAME_REQUIREMENTS)
683
720
684 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
721 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
685 controller='admin/repos', action='edit_fields',
722 controller='admin/repos', action='edit_fields',
686 conditions={'method': ['GET'], 'function': check_repo},
723 conditions={'method': ['GET'], 'function': check_repo},
687 requirements=URL_NAME_REQUIREMENTS)
724 requirements=URL_NAME_REQUIREMENTS)
688 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
725 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
689 controller='admin/repos', action='create_repo_field',
726 controller='admin/repos', action='create_repo_field',
690 conditions={'method': ['PUT'], 'function': check_repo},
727 conditions={'method': ['PUT'], 'function': check_repo},
691 requirements=URL_NAME_REQUIREMENTS)
728 requirements=URL_NAME_REQUIREMENTS)
692 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
729 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
693 controller='admin/repos', action='delete_repo_field',
730 controller='admin/repos', action='delete_repo_field',
694 conditions={'method': ['DELETE'], 'function': check_repo},
731 conditions={'method': ['DELETE'], 'function': check_repo},
695 requirements=URL_NAME_REQUIREMENTS)
732 requirements=URL_NAME_REQUIREMENTS)
696
733
697 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
734 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
698 controller='admin/repos', action='edit_advanced',
735 controller='admin/repos', action='edit_advanced',
699 conditions={'method': ['GET'], 'function': check_repo},
736 conditions={'method': ['GET'], 'function': check_repo},
700 requirements=URL_NAME_REQUIREMENTS)
737 requirements=URL_NAME_REQUIREMENTS)
701
738
702 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
739 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
703 controller='admin/repos', action='edit_advanced_locking',
740 controller='admin/repos', action='edit_advanced_locking',
704 conditions={'method': ['PUT'], 'function': check_repo},
741 conditions={'method': ['PUT'], 'function': check_repo},
705 requirements=URL_NAME_REQUIREMENTS)
742 requirements=URL_NAME_REQUIREMENTS)
706 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
743 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
707 controller='admin/repos', action='toggle_locking',
744 controller='admin/repos', action='toggle_locking',
708 conditions={'method': ['GET'], 'function': check_repo},
745 conditions={'method': ['GET'], 'function': check_repo},
709 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
710
747
711 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
748 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
712 controller='admin/repos', action='edit_advanced_journal',
749 controller='admin/repos', action='edit_advanced_journal',
713 conditions={'method': ['PUT'], 'function': check_repo},
750 conditions={'method': ['PUT'], 'function': check_repo},
714 requirements=URL_NAME_REQUIREMENTS)
751 requirements=URL_NAME_REQUIREMENTS)
715
752
716 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
753 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
717 controller='admin/repos', action='edit_advanced_fork',
754 controller='admin/repos', action='edit_advanced_fork',
718 conditions={'method': ['PUT'], 'function': check_repo},
755 conditions={'method': ['PUT'], 'function': check_repo},
719 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
720
757
721 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
758 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
722 controller='admin/repos', action='edit_caches_form',
759 controller='admin/repos', action='edit_caches_form',
723 conditions={'method': ['GET'], 'function': check_repo},
760 conditions={'method': ['GET'], 'function': check_repo},
724 requirements=URL_NAME_REQUIREMENTS)
761 requirements=URL_NAME_REQUIREMENTS)
725 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
762 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
726 controller='admin/repos', action='edit_caches',
763 controller='admin/repos', action='edit_caches',
727 conditions={'method': ['PUT'], 'function': check_repo},
764 conditions={'method': ['PUT'], 'function': check_repo},
728 requirements=URL_NAME_REQUIREMENTS)
765 requirements=URL_NAME_REQUIREMENTS)
729
766
730 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
767 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
731 controller='admin/repos', action='edit_remote_form',
768 controller='admin/repos', action='edit_remote_form',
732 conditions={'method': ['GET'], 'function': check_repo},
769 conditions={'method': ['GET'], 'function': check_repo},
733 requirements=URL_NAME_REQUIREMENTS)
770 requirements=URL_NAME_REQUIREMENTS)
734 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
771 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
735 controller='admin/repos', action='edit_remote',
772 controller='admin/repos', action='edit_remote',
736 conditions={'method': ['PUT'], 'function': check_repo},
773 conditions={'method': ['PUT'], 'function': check_repo},
737 requirements=URL_NAME_REQUIREMENTS)
774 requirements=URL_NAME_REQUIREMENTS)
738
775
739 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
776 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
740 controller='admin/repos', action='edit_statistics_form',
777 controller='admin/repos', action='edit_statistics_form',
741 conditions={'method': ['GET'], 'function': check_repo},
778 conditions={'method': ['GET'], 'function': check_repo},
742 requirements=URL_NAME_REQUIREMENTS)
779 requirements=URL_NAME_REQUIREMENTS)
743 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
780 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
744 controller='admin/repos', action='edit_statistics',
781 controller='admin/repos', action='edit_statistics',
745 conditions={'method': ['PUT'], 'function': check_repo},
782 conditions={'method': ['PUT'], 'function': check_repo},
746 requirements=URL_NAME_REQUIREMENTS)
783 requirements=URL_NAME_REQUIREMENTS)
747 rmap.connect('repo_settings_issuetracker',
784 rmap.connect('repo_settings_issuetracker',
748 '/{repo_name}/settings/issue-tracker',
785 '/{repo_name}/settings/issue-tracker',
749 controller='admin/repos', action='repo_issuetracker',
786 controller='admin/repos', action='repo_issuetracker',
750 conditions={'method': ['GET'], 'function': check_repo},
787 conditions={'method': ['GET'], 'function': check_repo},
751 requirements=URL_NAME_REQUIREMENTS)
788 requirements=URL_NAME_REQUIREMENTS)
752 rmap.connect('repo_issuetracker_test',
789 rmap.connect('repo_issuetracker_test',
753 '/{repo_name}/settings/issue-tracker/test',
790 '/{repo_name}/settings/issue-tracker/test',
754 controller='admin/repos', action='repo_issuetracker_test',
791 controller='admin/repos', action='repo_issuetracker_test',
755 conditions={'method': ['POST'], 'function': check_repo},
792 conditions={'method': ['POST'], 'function': check_repo},
756 requirements=URL_NAME_REQUIREMENTS)
793 requirements=URL_NAME_REQUIREMENTS)
757 rmap.connect('repo_issuetracker_delete',
794 rmap.connect('repo_issuetracker_delete',
758 '/{repo_name}/settings/issue-tracker/delete',
795 '/{repo_name}/settings/issue-tracker/delete',
759 controller='admin/repos', action='repo_issuetracker_delete',
796 controller='admin/repos', action='repo_issuetracker_delete',
760 conditions={'method': ['DELETE'], 'function': check_repo},
797 conditions={'method': ['DELETE'], 'function': check_repo},
761 requirements=URL_NAME_REQUIREMENTS)
798 requirements=URL_NAME_REQUIREMENTS)
762 rmap.connect('repo_issuetracker_save',
799 rmap.connect('repo_issuetracker_save',
763 '/{repo_name}/settings/issue-tracker/save',
800 '/{repo_name}/settings/issue-tracker/save',
764 controller='admin/repos', action='repo_issuetracker_save',
801 controller='admin/repos', action='repo_issuetracker_save',
765 conditions={'method': ['POST'], 'function': check_repo},
802 conditions={'method': ['POST'], 'function': check_repo},
766 requirements=URL_NAME_REQUIREMENTS)
803 requirements=URL_NAME_REQUIREMENTS)
767 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
804 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
768 controller='admin/repos', action='repo_settings_vcs_update',
805 controller='admin/repos', action='repo_settings_vcs_update',
769 conditions={'method': ['POST'], 'function': check_repo},
806 conditions={'method': ['POST'], 'function': check_repo},
770 requirements=URL_NAME_REQUIREMENTS)
807 requirements=URL_NAME_REQUIREMENTS)
771 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
808 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
772 controller='admin/repos', action='repo_settings_vcs',
809 controller='admin/repos', action='repo_settings_vcs',
773 conditions={'method': ['GET'], 'function': check_repo},
810 conditions={'method': ['GET'], 'function': check_repo},
774 requirements=URL_NAME_REQUIREMENTS)
811 requirements=URL_NAME_REQUIREMENTS)
775 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
812 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
776 controller='admin/repos', action='repo_delete_svn_pattern',
813 controller='admin/repos', action='repo_delete_svn_pattern',
777 conditions={'method': ['DELETE'], 'function': check_repo},
814 conditions={'method': ['DELETE'], 'function': check_repo},
778 requirements=URL_NAME_REQUIREMENTS)
815 requirements=URL_NAME_REQUIREMENTS)
779
816
780 # still working url for backward compat.
817 # still working url for backward compat.
781 rmap.connect('raw_changeset_home_depraced',
818 rmap.connect('raw_changeset_home_depraced',
782 '/{repo_name}/raw-changeset/{revision}',
819 '/{repo_name}/raw-changeset/{revision}',
783 controller='changeset', action='changeset_raw',
820 controller='changeset', action='changeset_raw',
784 revision='tip', conditions={'function': check_repo},
821 revision='tip', conditions={'function': check_repo},
785 requirements=URL_NAME_REQUIREMENTS)
822 requirements=URL_NAME_REQUIREMENTS)
786
823
787 # new URLs
824 # new URLs
788 rmap.connect('changeset_raw_home',
825 rmap.connect('changeset_raw_home',
789 '/{repo_name}/changeset-diff/{revision}',
826 '/{repo_name}/changeset-diff/{revision}',
790 controller='changeset', action='changeset_raw',
827 controller='changeset', action='changeset_raw',
791 revision='tip', conditions={'function': check_repo},
828 revision='tip', conditions={'function': check_repo},
792 requirements=URL_NAME_REQUIREMENTS)
829 requirements=URL_NAME_REQUIREMENTS)
793
830
794 rmap.connect('changeset_patch_home',
831 rmap.connect('changeset_patch_home',
795 '/{repo_name}/changeset-patch/{revision}',
832 '/{repo_name}/changeset-patch/{revision}',
796 controller='changeset', action='changeset_patch',
833 controller='changeset', action='changeset_patch',
797 revision='tip', conditions={'function': check_repo},
834 revision='tip', conditions={'function': check_repo},
798 requirements=URL_NAME_REQUIREMENTS)
835 requirements=URL_NAME_REQUIREMENTS)
799
836
800 rmap.connect('changeset_download_home',
837 rmap.connect('changeset_download_home',
801 '/{repo_name}/changeset-download/{revision}',
838 '/{repo_name}/changeset-download/{revision}',
802 controller='changeset', action='changeset_download',
839 controller='changeset', action='changeset_download',
803 revision='tip', conditions={'function': check_repo},
840 revision='tip', conditions={'function': check_repo},
804 requirements=URL_NAME_REQUIREMENTS)
841 requirements=URL_NAME_REQUIREMENTS)
805
842
806 rmap.connect('changeset_comment',
843 rmap.connect('changeset_comment',
807 '/{repo_name}/changeset/{revision}/comment',
844 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
808 controller='changeset', revision='tip', action='comment',
845 controller='changeset', revision='tip', action='comment',
809 conditions={'function': check_repo},
846 conditions={'function': check_repo},
810 requirements=URL_NAME_REQUIREMENTS)
847 requirements=URL_NAME_REQUIREMENTS)
811
848
812 rmap.connect('changeset_comment_preview',
849 rmap.connect('changeset_comment_preview',
813 '/{repo_name}/changeset/comment/preview',
850 '/{repo_name}/changeset/comment/preview', jsroute=True,
814 controller='changeset', action='preview_comment',
851 controller='changeset', action='preview_comment',
815 conditions={'function': check_repo, 'method': ['POST']},
852 conditions={'function': check_repo, 'method': ['POST']},
816 requirements=URL_NAME_REQUIREMENTS)
853 requirements=URL_NAME_REQUIREMENTS)
817
854
818 rmap.connect('changeset_comment_delete',
855 rmap.connect('changeset_comment_delete',
819 '/{repo_name}/changeset/comment/{comment_id}/delete',
856 '/{repo_name}/changeset/comment/{comment_id}/delete',
820 controller='changeset', action='delete_comment',
857 controller='changeset', action='delete_comment',
821 conditions={'function': check_repo, 'method': ['DELETE']},
858 conditions={'function': check_repo, 'method': ['DELETE']},
822 requirements=URL_NAME_REQUIREMENTS)
859 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
823
860
824 rmap.connect('changeset_info', '/changeset_info/{repo_name}/{revision}',
861 rmap.connect('changeset_info', '/changeset_info/{repo_name}/{revision}',
825 controller='changeset', action='changeset_info',
862 controller='changeset', action='changeset_info',
826 requirements=URL_NAME_REQUIREMENTS)
863 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
827
864
828 rmap.connect('compare_home',
865 rmap.connect('compare_home',
829 '/{repo_name}/compare',
866 '/{repo_name}/compare',
830 controller='compare', action='index',
867 controller='compare', action='index',
831 conditions={'function': check_repo},
868 conditions={'function': check_repo},
832 requirements=URL_NAME_REQUIREMENTS)
869 requirements=URL_NAME_REQUIREMENTS)
833
870
834 rmap.connect('compare_url',
871 rmap.connect('compare_url',
835 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
872 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
836 controller='compare', action='compare',
873 controller='compare', action='compare',
837 conditions={'function': check_repo},
874 conditions={'function': check_repo},
838 requirements=URL_NAME_REQUIREMENTS)
875 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
839
876
840 rmap.connect('pullrequest_home',
877 rmap.connect('pullrequest_home',
841 '/{repo_name}/pull-request/new', controller='pullrequests',
878 '/{repo_name}/pull-request/new', controller='pullrequests',
842 action='index', conditions={'function': check_repo,
879 action='index', conditions={'function': check_repo,
843 'method': ['GET']},
880 'method': ['GET']},
844 requirements=URL_NAME_REQUIREMENTS)
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
845
882
846 rmap.connect('pullrequest',
883 rmap.connect('pullrequest',
847 '/{repo_name}/pull-request/new', controller='pullrequests',
884 '/{repo_name}/pull-request/new', controller='pullrequests',
848 action='create', conditions={'function': check_repo,
885 action='create', conditions={'function': check_repo,
849 'method': ['POST']},
886 'method': ['POST']},
850 requirements=URL_NAME_REQUIREMENTS)
887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
851
888
852 rmap.connect('pullrequest_repo_refs',
889 rmap.connect('pullrequest_repo_refs',
853 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
890 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
854 controller='pullrequests',
891 controller='pullrequests',
855 action='get_repo_refs',
892 action='get_repo_refs',
856 conditions={'function': check_repo, 'method': ['GET']},
893 conditions={'function': check_repo, 'method': ['GET']},
857 requirements=URL_NAME_REQUIREMENTS)
894 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
858
895
859 rmap.connect('pullrequest_repo_destinations',
896 rmap.connect('pullrequest_repo_destinations',
860 '/{repo_name}/pull-request/repo-destinations',
897 '/{repo_name}/pull-request/repo-destinations',
861 controller='pullrequests',
898 controller='pullrequests',
862 action='get_repo_destinations',
899 action='get_repo_destinations',
863 conditions={'function': check_repo, 'method': ['GET']},
900 conditions={'function': check_repo, 'method': ['GET']},
864 requirements=URL_NAME_REQUIREMENTS)
901 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
865
902
866 rmap.connect('pullrequest_show',
903 rmap.connect('pullrequest_show',
867 '/{repo_name}/pull-request/{pull_request_id}',
904 '/{repo_name}/pull-request/{pull_request_id}',
868 controller='pullrequests',
905 controller='pullrequests',
869 action='show', conditions={'function': check_repo,
906 action='show', conditions={'function': check_repo,
870 'method': ['GET']},
907 'method': ['GET']},
871 requirements=URL_NAME_REQUIREMENTS)
908 requirements=URL_NAME_REQUIREMENTS)
872
909
873 rmap.connect('pullrequest_update',
910 rmap.connect('pullrequest_update',
874 '/{repo_name}/pull-request/{pull_request_id}',
911 '/{repo_name}/pull-request/{pull_request_id}',
875 controller='pullrequests',
912 controller='pullrequests',
876 action='update', conditions={'function': check_repo,
913 action='update', conditions={'function': check_repo,
877 'method': ['PUT']},
914 'method': ['PUT']},
878 requirements=URL_NAME_REQUIREMENTS)
915 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
879
916
880 rmap.connect('pullrequest_merge',
917 rmap.connect('pullrequest_merge',
881 '/{repo_name}/pull-request/{pull_request_id}',
918 '/{repo_name}/pull-request/{pull_request_id}',
882 controller='pullrequests',
919 controller='pullrequests',
883 action='merge', conditions={'function': check_repo,
920 action='merge', conditions={'function': check_repo,
884 'method': ['POST']},
921 'method': ['POST']},
885 requirements=URL_NAME_REQUIREMENTS)
922 requirements=URL_NAME_REQUIREMENTS)
886
923
887 rmap.connect('pullrequest_delete',
924 rmap.connect('pullrequest_delete',
888 '/{repo_name}/pull-request/{pull_request_id}',
925 '/{repo_name}/pull-request/{pull_request_id}',
889 controller='pullrequests',
926 controller='pullrequests',
890 action='delete', conditions={'function': check_repo,
927 action='delete', conditions={'function': check_repo,
891 'method': ['DELETE']},
928 'method': ['DELETE']},
892 requirements=URL_NAME_REQUIREMENTS)
929 requirements=URL_NAME_REQUIREMENTS)
893
930
894 rmap.connect('pullrequest_show_all',
931 rmap.connect('pullrequest_show_all',
895 '/{repo_name}/pull-request',
932 '/{repo_name}/pull-request',
896 controller='pullrequests',
933 controller='pullrequests',
897 action='show_all', conditions={'function': check_repo,
934 action='show_all', conditions={'function': check_repo,
898 'method': ['GET']},
935 'method': ['GET']},
899 requirements=URL_NAME_REQUIREMENTS)
936 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
900
937
901 rmap.connect('pullrequest_comment',
938 rmap.connect('pullrequest_comment',
902 '/{repo_name}/pull-request-comment/{pull_request_id}',
939 '/{repo_name}/pull-request-comment/{pull_request_id}',
903 controller='pullrequests',
940 controller='pullrequests',
904 action='comment', conditions={'function': check_repo,
941 action='comment', conditions={'function': check_repo,
905 'method': ['POST']},
942 'method': ['POST']},
906 requirements=URL_NAME_REQUIREMENTS)
943 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
907
944
908 rmap.connect('pullrequest_comment_delete',
945 rmap.connect('pullrequest_comment_delete',
909 '/{repo_name}/pull-request-comment/{comment_id}/delete',
946 '/{repo_name}/pull-request-comment/{comment_id}/delete',
910 controller='pullrequests', action='delete_comment',
947 controller='pullrequests', action='delete_comment',
911 conditions={'function': check_repo, 'method': ['DELETE']},
948 conditions={'function': check_repo, 'method': ['DELETE']},
912 requirements=URL_NAME_REQUIREMENTS)
949 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
913
950
914 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
951 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
915 controller='summary', conditions={'function': check_repo},
952 controller='summary', conditions={'function': check_repo},
916 requirements=URL_NAME_REQUIREMENTS)
953 requirements=URL_NAME_REQUIREMENTS)
917
954
918 rmap.connect('branches_home', '/{repo_name}/branches',
955 rmap.connect('branches_home', '/{repo_name}/branches',
919 controller='branches', conditions={'function': check_repo},
956 controller='branches', conditions={'function': check_repo},
920 requirements=URL_NAME_REQUIREMENTS)
957 requirements=URL_NAME_REQUIREMENTS)
921
958
922 rmap.connect('tags_home', '/{repo_name}/tags',
959 rmap.connect('tags_home', '/{repo_name}/tags',
923 controller='tags', conditions={'function': check_repo},
960 controller='tags', conditions={'function': check_repo},
924 requirements=URL_NAME_REQUIREMENTS)
961 requirements=URL_NAME_REQUIREMENTS)
925
962
926 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
963 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
927 controller='bookmarks', conditions={'function': check_repo},
964 controller='bookmarks', conditions={'function': check_repo},
928 requirements=URL_NAME_REQUIREMENTS)
965 requirements=URL_NAME_REQUIREMENTS)
929
966
930 rmap.connect('changelog_home', '/{repo_name}/changelog',
967 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
931 controller='changelog', conditions={'function': check_repo},
968 controller='changelog', conditions={'function': check_repo},
932 requirements=URL_NAME_REQUIREMENTS)
969 requirements=URL_NAME_REQUIREMENTS)
933
970
934 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
971 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
935 controller='changelog', action='changelog_summary',
972 controller='changelog', action='changelog_summary',
936 conditions={'function': check_repo},
973 conditions={'function': check_repo},
937 requirements=URL_NAME_REQUIREMENTS)
974 requirements=URL_NAME_REQUIREMENTS)
938
975
939 rmap.connect('changelog_file_home', '/{repo_name}/changelog/{revision}/{f_path}',
976 rmap.connect('changelog_file_home',
977 '/{repo_name}/changelog/{revision}/{f_path}',
940 controller='changelog', f_path=None,
978 controller='changelog', f_path=None,
941 conditions={'function': check_repo},
979 conditions={'function': check_repo},
942 requirements=URL_NAME_REQUIREMENTS)
980 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
943
981
944 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
982 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
945 controller='changelog', action='changelog_details',
983 controller='changelog', action='changelog_details',
946 conditions={'function': check_repo},
984 conditions={'function': check_repo},
947 requirements=URL_NAME_REQUIREMENTS)
985 requirements=URL_NAME_REQUIREMENTS)
948
986
949 rmap.connect('files_home',
987 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
950 '/{repo_name}/files/{revision}/{f_path}',
951 controller='files', revision='tip', f_path='',
988 controller='files', revision='tip', f_path='',
952 conditions={'function': check_repo},
989 conditions={'function': check_repo},
953 requirements=URL_NAME_REQUIREMENTS)
990 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
954
991
955 rmap.connect('files_home_simple_catchrev',
992 rmap.connect('files_home_simple_catchrev',
956 '/{repo_name}/files/{revision}',
993 '/{repo_name}/files/{revision}',
957 controller='files', revision='tip', f_path='',
994 controller='files', revision='tip', f_path='',
958 conditions={'function': check_repo},
995 conditions={'function': check_repo},
959 requirements=URL_NAME_REQUIREMENTS)
996 requirements=URL_NAME_REQUIREMENTS)
960
997
961 rmap.connect('files_home_simple_catchall',
998 rmap.connect('files_home_simple_catchall',
962 '/{repo_name}/files',
999 '/{repo_name}/files',
963 controller='files', revision='tip', f_path='',
1000 controller='files', revision='tip', f_path='',
964 conditions={'function': check_repo},
1001 conditions={'function': check_repo},
965 requirements=URL_NAME_REQUIREMENTS)
1002 requirements=URL_NAME_REQUIREMENTS)
966
1003
967 rmap.connect('files_history_home',
1004 rmap.connect('files_history_home',
968 '/{repo_name}/history/{revision}/{f_path}',
1005 '/{repo_name}/history/{revision}/{f_path}',
969 controller='files', action='history', revision='tip', f_path='',
1006 controller='files', action='history', revision='tip', f_path='',
970 conditions={'function': check_repo},
1007 conditions={'function': check_repo},
971 requirements=URL_NAME_REQUIREMENTS)
1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
972
1009
973 rmap.connect('files_authors_home',
1010 rmap.connect('files_authors_home',
974 '/{repo_name}/authors/{revision}/{f_path}',
1011 '/{repo_name}/authors/{revision}/{f_path}',
975 controller='files', action='authors', revision='tip', f_path='',
1012 controller='files', action='authors', revision='tip', f_path='',
976 conditions={'function': check_repo},
1013 conditions={'function': check_repo},
977 requirements=URL_NAME_REQUIREMENTS)
1014 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
978
1015
979 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1016 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
980 controller='files', action='diff', f_path='',
1017 controller='files', action='diff', f_path='',
981 conditions={'function': check_repo},
1018 conditions={'function': check_repo},
982 requirements=URL_NAME_REQUIREMENTS)
1019 requirements=URL_NAME_REQUIREMENTS)
983
1020
984 rmap.connect('files_diff_2way_home',
1021 rmap.connect('files_diff_2way_home',
985 '/{repo_name}/diff-2way/{f_path}',
1022 '/{repo_name}/diff-2way/{f_path}',
986 controller='files', action='diff_2way', f_path='',
1023 controller='files', action='diff_2way', f_path='',
987 conditions={'function': check_repo},
1024 conditions={'function': check_repo},
988 requirements=URL_NAME_REQUIREMENTS)
1025 requirements=URL_NAME_REQUIREMENTS)
989
1026
990 rmap.connect('files_rawfile_home',
1027 rmap.connect('files_rawfile_home',
991 '/{repo_name}/rawfile/{revision}/{f_path}',
1028 '/{repo_name}/rawfile/{revision}/{f_path}',
992 controller='files', action='rawfile', revision='tip',
1029 controller='files', action='rawfile', revision='tip',
993 f_path='', conditions={'function': check_repo},
1030 f_path='', conditions={'function': check_repo},
994 requirements=URL_NAME_REQUIREMENTS)
1031 requirements=URL_NAME_REQUIREMENTS)
995
1032
996 rmap.connect('files_raw_home',
1033 rmap.connect('files_raw_home',
997 '/{repo_name}/raw/{revision}/{f_path}',
1034 '/{repo_name}/raw/{revision}/{f_path}',
998 controller='files', action='raw', revision='tip', f_path='',
1035 controller='files', action='raw', revision='tip', f_path='',
999 conditions={'function': check_repo},
1036 conditions={'function': check_repo},
1000 requirements=URL_NAME_REQUIREMENTS)
1037 requirements=URL_NAME_REQUIREMENTS)
1001
1038
1002 rmap.connect('files_render_home',
1039 rmap.connect('files_render_home',
1003 '/{repo_name}/render/{revision}/{f_path}',
1040 '/{repo_name}/render/{revision}/{f_path}',
1004 controller='files', action='index', revision='tip', f_path='',
1041 controller='files', action='index', revision='tip', f_path='',
1005 rendered=True, conditions={'function': check_repo},
1042 rendered=True, conditions={'function': check_repo},
1006 requirements=URL_NAME_REQUIREMENTS)
1043 requirements=URL_NAME_REQUIREMENTS)
1007
1044
1008 rmap.connect('files_annotate_home',
1045 rmap.connect('files_annotate_home',
1009 '/{repo_name}/annotate/{revision}/{f_path}',
1046 '/{repo_name}/annotate/{revision}/{f_path}',
1010 controller='files', action='index', revision='tip',
1047 controller='files', action='index', revision='tip',
1011 f_path='', annotate=True, conditions={'function': check_repo},
1048 f_path='', annotate=True, conditions={'function': check_repo},
1012 requirements=URL_NAME_REQUIREMENTS)
1049 requirements=URL_NAME_REQUIREMENTS)
1013
1050
1014 rmap.connect('files_edit',
1051 rmap.connect('files_edit',
1015 '/{repo_name}/edit/{revision}/{f_path}',
1052 '/{repo_name}/edit/{revision}/{f_path}',
1016 controller='files', action='edit', revision='tip',
1053 controller='files', action='edit', revision='tip',
1017 f_path='',
1054 f_path='',
1018 conditions={'function': check_repo, 'method': ['POST']},
1055 conditions={'function': check_repo, 'method': ['POST']},
1019 requirements=URL_NAME_REQUIREMENTS)
1056 requirements=URL_NAME_REQUIREMENTS)
1020
1057
1021 rmap.connect('files_edit_home',
1058 rmap.connect('files_edit_home',
1022 '/{repo_name}/edit/{revision}/{f_path}',
1059 '/{repo_name}/edit/{revision}/{f_path}',
1023 controller='files', action='edit_home', revision='tip',
1060 controller='files', action='edit_home', revision='tip',
1024 f_path='', conditions={'function': check_repo},
1061 f_path='', conditions={'function': check_repo},
1025 requirements=URL_NAME_REQUIREMENTS)
1062 requirements=URL_NAME_REQUIREMENTS)
1026
1063
1027 rmap.connect('files_add',
1064 rmap.connect('files_add',
1028 '/{repo_name}/add/{revision}/{f_path}',
1065 '/{repo_name}/add/{revision}/{f_path}',
1029 controller='files', action='add', revision='tip',
1066 controller='files', action='add', revision='tip',
1030 f_path='',
1067 f_path='',
1031 conditions={'function': check_repo, 'method': ['POST']},
1068 conditions={'function': check_repo, 'method': ['POST']},
1032 requirements=URL_NAME_REQUIREMENTS)
1069 requirements=URL_NAME_REQUIREMENTS)
1033
1070
1034 rmap.connect('files_add_home',
1071 rmap.connect('files_add_home',
1035 '/{repo_name}/add/{revision}/{f_path}',
1072 '/{repo_name}/add/{revision}/{f_path}',
1036 controller='files', action='add_home', revision='tip',
1073 controller='files', action='add_home', revision='tip',
1037 f_path='', conditions={'function': check_repo},
1074 f_path='', conditions={'function': check_repo},
1038 requirements=URL_NAME_REQUIREMENTS)
1075 requirements=URL_NAME_REQUIREMENTS)
1039
1076
1040 rmap.connect('files_delete',
1077 rmap.connect('files_delete',
1041 '/{repo_name}/delete/{revision}/{f_path}',
1078 '/{repo_name}/delete/{revision}/{f_path}',
1042 controller='files', action='delete', revision='tip',
1079 controller='files', action='delete', revision='tip',
1043 f_path='',
1080 f_path='',
1044 conditions={'function': check_repo, 'method': ['POST']},
1081 conditions={'function': check_repo, 'method': ['POST']},
1045 requirements=URL_NAME_REQUIREMENTS)
1082 requirements=URL_NAME_REQUIREMENTS)
1046
1083
1047 rmap.connect('files_delete_home',
1084 rmap.connect('files_delete_home',
1048 '/{repo_name}/delete/{revision}/{f_path}',
1085 '/{repo_name}/delete/{revision}/{f_path}',
1049 controller='files', action='delete_home', revision='tip',
1086 controller='files', action='delete_home', revision='tip',
1050 f_path='', conditions={'function': check_repo},
1087 f_path='', conditions={'function': check_repo},
1051 requirements=URL_NAME_REQUIREMENTS)
1088 requirements=URL_NAME_REQUIREMENTS)
1052
1089
1053 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1090 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1054 controller='files', action='archivefile',
1091 controller='files', action='archivefile',
1055 conditions={'function': check_repo},
1092 conditions={'function': check_repo},
1056 requirements=URL_NAME_REQUIREMENTS)
1093 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1057
1094
1058 rmap.connect('files_nodelist_home',
1095 rmap.connect('files_nodelist_home',
1059 '/{repo_name}/nodelist/{revision}/{f_path}',
1096 '/{repo_name}/nodelist/{revision}/{f_path}',
1060 controller='files', action='nodelist',
1097 controller='files', action='nodelist',
1061 conditions={'function': check_repo},
1098 conditions={'function': check_repo},
1062 requirements=URL_NAME_REQUIREMENTS)
1099 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1063
1100
1064 rmap.connect('files_metadata_list_home',
1101 rmap.connect('files_metadata_list_home',
1065 '/{repo_name}/metadata_list/{revision}/{f_path}',
1102 '/{repo_name}/metadata_list/{revision}/{f_path}',
1066 controller='files', action='metadata_list',
1103 controller='files', action='metadata_list',
1067 conditions={'function': check_repo},
1104 conditions={'function': check_repo},
1068 requirements=URL_NAME_REQUIREMENTS)
1105 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1069
1106
1070 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1107 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1071 controller='forks', action='fork_create',
1108 controller='forks', action='fork_create',
1072 conditions={'function': check_repo, 'method': ['POST']},
1109 conditions={'function': check_repo, 'method': ['POST']},
1073 requirements=URL_NAME_REQUIREMENTS)
1110 requirements=URL_NAME_REQUIREMENTS)
1074
1111
1075 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1112 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1076 controller='forks', action='fork',
1113 controller='forks', action='fork',
1077 conditions={'function': check_repo},
1114 conditions={'function': check_repo},
1078 requirements=URL_NAME_REQUIREMENTS)
1115 requirements=URL_NAME_REQUIREMENTS)
1079
1116
1080 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1117 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1081 controller='forks', action='forks',
1118 controller='forks', action='forks',
1082 conditions={'function': check_repo},
1119 conditions={'function': check_repo},
1083 requirements=URL_NAME_REQUIREMENTS)
1120 requirements=URL_NAME_REQUIREMENTS)
1084
1121
1085 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1122 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1086 controller='followers', action='followers',
1123 controller='followers', action='followers',
1087 conditions={'function': check_repo},
1124 conditions={'function': check_repo},
1088 requirements=URL_NAME_REQUIREMENTS)
1125 requirements=URL_NAME_REQUIREMENTS)
1089
1126
1090 # must be here for proper group/repo catching pattern
1127 # must be here for proper group/repo catching pattern
1091 _connect_with_slash(
1128 _connect_with_slash(
1092 rmap, 'repo_group_home', '/{group_name}',
1129 rmap, 'repo_group_home', '/{group_name}',
1093 controller='home', action='index_repo_group',
1130 controller='home', action='index_repo_group',
1094 conditions={'function': check_group},
1131 conditions={'function': check_group},
1095 requirements=URL_NAME_REQUIREMENTS)
1132 requirements=URL_NAME_REQUIREMENTS)
1096
1133
1097 # catch all, at the end
1134 # catch all, at the end
1098 _connect_with_slash(
1135 _connect_with_slash(
1099 rmap, 'summary_home', '/{repo_name}',
1136 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1100 controller='summary', action='index',
1137 controller='summary', action='index',
1101 conditions={'function': check_repo},
1138 conditions={'function': check_repo},
1102 requirements=URL_NAME_REQUIREMENTS)
1139 requirements=URL_NAME_REQUIREMENTS)
1103
1140
1104 return rmap
1141 return rmap
1105
1142
1106
1143
1107 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1144 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1108 """
1145 """
1109 Connect a route with an optional trailing slash in `path`.
1146 Connect a route with an optional trailing slash in `path`.
1110 """
1147 """
1111 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1148 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1112 mapper.connect(name, path, *args, **kwargs)
1149 mapper.connect(name, path, *args, **kwargs)
@@ -1,87 +1,99 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import shlex
22 import shlex
23 import Pyro4
23 import Pyro4
24 import platform
24 import platform
25
25
26 from rhodecode.model import init_model
26 from rhodecode.model import init_model
27
27
28
28
29 def configure_pyro4(config):
29 def configure_pyro4(config):
30 """
30 """
31 Configure Pyro4 based on `config`.
31 Configure Pyro4 based on `config`.
32
32
33 This will mainly set the different configuration parameters of the Pyro4
33 This will mainly set the different configuration parameters of the Pyro4
34 library based on the settings in our INI files. The Pyro4 documentation
34 library based on the settings in our INI files. The Pyro4 documentation
35 lists more details about the specific settings and their meaning.
35 lists more details about the specific settings and their meaning.
36 """
36 """
37 Pyro4.config.COMMTIMEOUT = float(config['vcs.connection_timeout'])
37 Pyro4.config.COMMTIMEOUT = float(config['vcs.connection_timeout'])
38 Pyro4.config.SERIALIZER = 'pickle'
38 Pyro4.config.SERIALIZER = 'pickle'
39 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
39 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
40
40
41 # Note: We need server configuration in the WSGI processes
41 # Note: We need server configuration in the WSGI processes
42 # because we provide a callback server in certain vcs operations.
42 # because we provide a callback server in certain vcs operations.
43 Pyro4.config.SERVERTYPE = "multiplex"
43 Pyro4.config.SERVERTYPE = "multiplex"
44 Pyro4.config.POLLTIMEOUT = 0.01
44 Pyro4.config.POLLTIMEOUT = 0.01
45
45
46
46
47 def configure_vcs(config):
47 def configure_vcs(config):
48 """
48 """
49 Patch VCS config with some RhodeCode specific stuff
49 Patch VCS config with some RhodeCode specific stuff
50 """
50 """
51 from rhodecode.lib.vcs import conf
51 from rhodecode.lib.vcs import conf
52 from rhodecode.lib.utils2 import aslist
52 from rhodecode.lib.utils2 import aslist
53 conf.settings.BACKENDS = {
53 conf.settings.BACKENDS = {
54 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
54 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
55 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
55 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
56 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
56 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
57 }
57 }
58
58
59 conf.settings.HG_USE_REBASE_FOR_MERGING = config.get(
59 conf.settings.HG_USE_REBASE_FOR_MERGING = config.get(
60 'rhodecode_hg_use_rebase_for_merging', False)
60 'rhodecode_hg_use_rebase_for_merging', False)
61 conf.settings.GIT_REV_FILTER = shlex.split(
61 conf.settings.GIT_REV_FILTER = shlex.split(
62 config.get('git_rev_filter', '--all').strip())
62 config.get('git_rev_filter', '--all').strip())
63 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
63 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
64 'UTF-8'), sep=',')
64 'UTF-8'), sep=',')
65 conf.settings.ALIASES[:] = config.get('vcs.backends')
65 conf.settings.ALIASES[:] = config.get('vcs.backends')
66 conf.settings.SVN_COMPATIBLE_VERSION = config.get(
66 conf.settings.SVN_COMPATIBLE_VERSION = config.get(
67 'vcs.svn.compatible_version')
67 'vcs.svn.compatible_version')
68
68
69
69
70 def initialize_database(config):
70 def initialize_database(config):
71 from rhodecode.lib.utils2 import engine_from_config
71 from rhodecode.lib.utils2 import engine_from_config
72 engine = engine_from_config(config, 'sqlalchemy.db1.')
72 engine = engine_from_config(config, 'sqlalchemy.db1.')
73 init_model(engine, encryption_key=config['beaker.session.secret'])
73 init_model(engine, encryption_key=config['beaker.session.secret'])
74
74
75
75
76 def initialize_test_environment(settings, test_env=None):
77 if test_env is None:
78 test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
79
80 from rhodecode.lib.utils import create_test_env, create_test_index
81 from rhodecode.tests import TESTS_TMP_PATH
82 # test repos
83 if test_env:
84 create_test_env(TESTS_TMP_PATH, settings)
85 create_test_index(TESTS_TMP_PATH, settings, True)
86
87
76 def get_vcs_server_protocol(config):
88 def get_vcs_server_protocol(config):
77 protocol = config.get('vcs.server.protocol', 'pyro4')
89 protocol = config.get('vcs.server.protocol', 'pyro4')
78 return protocol
90 return protocol
79
91
80
92
81 def set_instance_id(config):
93 def set_instance_id(config):
82 """ Sets a dynamic generated config['instance_id'] if missing or '*' """
94 """ Sets a dynamic generated config['instance_id'] if missing or '*' """
83
95
84 config['instance_id'] = config.get('instance_id') or ''
96 config['instance_id'] = config.get('instance_id') or ''
85 if config['instance_id'] == '*' or not config['instance_id']:
97 if config['instance_id'] == '*' or not config['instance_id']:
86 _platform_id = platform.uname()[1] or 'instance'
98 _platform_id = platform.uname()[1] or 'instance'
87 config['instance_id'] = '%s-%s' % (_platform_id, os.getpid())
99 config['instance_id'] = '%s-%s' % (_platform_id, os.getpid())
@@ -1,373 +1,348 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2016 RhodeCode GmbH
3 # Copyright (C) 2013-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27
27
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, session
30 from pylons import request, tmpl_context as c, url, session
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34
34
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
38 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.utils2 import safe_int, md5
40 from rhodecode.lib.utils2 import safe_int, md5
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.model.db import (Repository, PullRequest, PullRequestReviewers,
42 from rhodecode.model.db import (
43 UserEmailMap, User, UserFollowing,
43 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
44 ExternalIdentity)
44 UserFollowing)
45 from rhodecode.model.forms import UserForm, PasswordChangeForm
45 from rhodecode.model.forms import UserForm, PasswordChangeForm
46 from rhodecode.model.scm import RepoList
46 from rhodecode.model.scm import RepoList
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.auth_token import AuthTokenModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.settings import SettingsModel
52
51
53 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
54
53
55
54
56 class MyAccountController(BaseController):
55 class MyAccountController(BaseController):
57 """REST Controller styled on the Atom Publishing Protocol"""
56 """REST Controller styled on the Atom Publishing Protocol"""
58 # To properly map this controller, ensure your config/routing.py
57 # To properly map this controller, ensure your config/routing.py
59 # file has a resource setup:
58 # file has a resource setup:
60 # map.resource('setting', 'settings', controller='admin/settings',
59 # map.resource('setting', 'settings', controller='admin/settings',
61 # path_prefix='/admin', name_prefix='admin_')
60 # path_prefix='/admin', name_prefix='admin_')
62
61
63 @LoginRequired()
62 @LoginRequired()
64 @NotAnonymous()
63 @NotAnonymous()
65 def __before__(self):
64 def __before__(self):
66 super(MyAccountController, self).__before__()
65 super(MyAccountController, self).__before__()
67
66
68 def __load_data(self):
67 def __load_data(self):
69 c.user = User.get(c.rhodecode_user.user_id)
68 c.user = User.get(c.rhodecode_user.user_id)
70 if c.user.username == User.DEFAULT_USER:
69 if c.user.username == User.DEFAULT_USER:
71 h.flash(_("You can't edit this user since it's"
70 h.flash(_("You can't edit this user since it's"
72 " crucial for entire application"), category='warning')
71 " crucial for entire application"), category='warning')
73 return redirect(url('users'))
72 return redirect(url('users'))
74
73
75 def _load_my_repos_data(self, watched=False):
74 def _load_my_repos_data(self, watched=False):
76 if watched:
75 if watched:
77 admin = False
76 admin = False
78 follows_repos = Session().query(UserFollowing)\
77 follows_repos = Session().query(UserFollowing)\
79 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
78 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
80 .options(joinedload(UserFollowing.follows_repository))\
79 .options(joinedload(UserFollowing.follows_repository))\
81 .all()
80 .all()
82 repo_list = [x.follows_repository for x in follows_repos]
81 repo_list = [x.follows_repository for x in follows_repos]
83 else:
82 else:
84 admin = True
83 admin = True
85 repo_list = Repository.get_all_repos(
84 repo_list = Repository.get_all_repos(
86 user_id=c.rhodecode_user.user_id)
85 user_id=c.rhodecode_user.user_id)
87 repo_list = RepoList(repo_list, perm_set=[
86 repo_list = RepoList(repo_list, perm_set=[
88 'repository.read', 'repository.write', 'repository.admin'])
87 'repository.read', 'repository.write', 'repository.admin'])
89
88
90 repos_data = RepoModel().get_repos_as_dict(
89 repos_data = RepoModel().get_repos_as_dict(
91 repo_list=repo_list, admin=admin)
90 repo_list=repo_list, admin=admin)
92 # json used to render the grid
91 # json used to render the grid
93 return json.dumps(repos_data)
92 return json.dumps(repos_data)
94
93
95 @auth.CSRFRequired()
94 @auth.CSRFRequired()
96 def my_account_update(self):
95 def my_account_update(self):
97 """
96 """
98 POST /_admin/my_account Updates info of my account
97 POST /_admin/my_account Updates info of my account
99 """
98 """
100 # url('my_account')
99 # url('my_account')
101 c.active = 'profile_edit'
100 c.active = 'profile_edit'
102 self.__load_data()
101 self.__load_data()
103 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
102 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
104 ip_addr=self.ip_addr)
103 ip_addr=self.ip_addr)
105 c.extern_type = c.user.extern_type
104 c.extern_type = c.user.extern_type
106 c.extern_name = c.user.extern_name
105 c.extern_name = c.user.extern_name
107
106
108 defaults = c.user.get_dict()
107 defaults = c.user.get_dict()
109 update = False
108 update = False
110 _form = UserForm(edit=True,
109 _form = UserForm(edit=True,
111 old_data={'user_id': c.rhodecode_user.user_id,
110 old_data={'user_id': c.rhodecode_user.user_id,
112 'email': c.rhodecode_user.email})()
111 'email': c.rhodecode_user.email})()
113 form_result = {}
112 form_result = {}
114 try:
113 try:
115 post_data = dict(request.POST)
114 post_data = dict(request.POST)
116 post_data['new_password'] = ''
115 post_data['new_password'] = ''
117 post_data['password_confirmation'] = ''
116 post_data['password_confirmation'] = ''
118 form_result = _form.to_python(post_data)
117 form_result = _form.to_python(post_data)
119 # skip updating those attrs for my account
118 # skip updating those attrs for my account
120 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
121 'new_password', 'password_confirmation']
120 'new_password', 'password_confirmation']
122 # TODO: plugin should define if username can be updated
121 # TODO: plugin should define if username can be updated
123 if c.extern_type != "rhodecode":
122 if c.extern_type != "rhodecode":
124 # forbid updating username for external accounts
123 # forbid updating username for external accounts
125 skip_attrs.append('username')
124 skip_attrs.append('username')
126
125
127 UserModel().update_user(
126 UserModel().update_user(
128 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
127 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
129 h.flash(_('Your account was updated successfully'),
128 h.flash(_('Your account was updated successfully'),
130 category='success')
129 category='success')
131 Session().commit()
130 Session().commit()
132 update = True
131 update = True
133
132
134 except formencode.Invalid as errors:
133 except formencode.Invalid as errors:
135 return htmlfill.render(
134 return htmlfill.render(
136 render('admin/my_account/my_account.html'),
135 render('admin/my_account/my_account.html'),
137 defaults=errors.value,
136 defaults=errors.value,
138 errors=errors.error_dict or {},
137 errors=errors.error_dict or {},
139 prefix_error=False,
138 prefix_error=False,
140 encoding="UTF-8",
139 encoding="UTF-8",
141 force_defaults=False)
140 force_defaults=False)
142 except Exception:
141 except Exception:
143 log.exception("Exception updating user")
142 log.exception("Exception updating user")
144 h.flash(_('Error occurred during update of user %s')
143 h.flash(_('Error occurred during update of user %s')
145 % form_result.get('username'), category='error')
144 % form_result.get('username'), category='error')
146
145
147 if update:
146 if update:
148 return redirect('my_account')
147 return redirect('my_account')
149
148
150 return htmlfill.render(
149 return htmlfill.render(
151 render('admin/my_account/my_account.html'),
150 render('admin/my_account/my_account.html'),
152 defaults=defaults,
151 defaults=defaults,
153 encoding="UTF-8",
152 encoding="UTF-8",
154 force_defaults=False
153 force_defaults=False
155 )
154 )
156
155
157 def my_account(self):
156 def my_account(self):
158 """
157 """
159 GET /_admin/my_account Displays info about my account
158 GET /_admin/my_account Displays info about my account
160 """
159 """
161 # url('my_account')
160 # url('my_account')
162 c.active = 'profile'
161 c.active = 'profile'
163 self.__load_data()
162 self.__load_data()
164
163
165 defaults = c.user.get_dict()
164 defaults = c.user.get_dict()
166 return htmlfill.render(
165 return htmlfill.render(
167 render('admin/my_account/my_account.html'),
166 render('admin/my_account/my_account.html'),
168 defaults=defaults, encoding="UTF-8", force_defaults=False)
167 defaults=defaults, encoding="UTF-8", force_defaults=False)
169
168
170 def my_account_edit(self):
169 def my_account_edit(self):
171 """
170 """
172 GET /_admin/my_account/edit Displays edit form of my account
171 GET /_admin/my_account/edit Displays edit form of my account
173 """
172 """
174 c.active = 'profile_edit'
173 c.active = 'profile_edit'
175 self.__load_data()
174 self.__load_data()
176 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
175 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
177 ip_addr=self.ip_addr)
176 ip_addr=self.ip_addr)
178 c.extern_type = c.user.extern_type
177 c.extern_type = c.user.extern_type
179 c.extern_name = c.user.extern_name
178 c.extern_name = c.user.extern_name
180
179
181 defaults = c.user.get_dict()
180 defaults = c.user.get_dict()
182 return htmlfill.render(
181 return htmlfill.render(
183 render('admin/my_account/my_account.html'),
182 render('admin/my_account/my_account.html'),
184 defaults=defaults,
183 defaults=defaults,
185 encoding="UTF-8",
184 encoding="UTF-8",
186 force_defaults=False
185 force_defaults=False
187 )
186 )
188
187
189 @auth.CSRFRequired()
188 @auth.CSRFRequired()
190 def my_account_password_update(self):
189 def my_account_password_update(self):
191 c.active = 'password'
190 c.active = 'password'
192 self.__load_data()
191 self.__load_data()
193 _form = PasswordChangeForm(c.rhodecode_user.username)()
192 _form = PasswordChangeForm(c.rhodecode_user.username)()
194 try:
193 try:
195 form_result = _form.to_python(request.POST)
194 form_result = _form.to_python(request.POST)
196 UserModel().update_user(c.rhodecode_user.user_id, **form_result)
195 UserModel().update_user(c.rhodecode_user.user_id, **form_result)
197 instance = c.rhodecode_user.get_instance()
196 instance = c.rhodecode_user.get_instance()
198 instance.update_userdata(force_password_change=False)
197 instance.update_userdata(force_password_change=False)
199 Session().commit()
198 Session().commit()
200 session.setdefault('rhodecode_user', {}).update(
199 session.setdefault('rhodecode_user', {}).update(
201 {'password': md5(instance.password)})
200 {'password': md5(instance.password)})
202 session.save()
201 session.save()
203 h.flash(_("Successfully updated password"), category='success')
202 h.flash(_("Successfully updated password"), category='success')
204 except formencode.Invalid as errors:
203 except formencode.Invalid as errors:
205 return htmlfill.render(
204 return htmlfill.render(
206 render('admin/my_account/my_account.html'),
205 render('admin/my_account/my_account.html'),
207 defaults=errors.value,
206 defaults=errors.value,
208 errors=errors.error_dict or {},
207 errors=errors.error_dict or {},
209 prefix_error=False,
208 prefix_error=False,
210 encoding="UTF-8",
209 encoding="UTF-8",
211 force_defaults=False)
210 force_defaults=False)
212 except Exception:
211 except Exception:
213 log.exception("Exception updating password")
212 log.exception("Exception updating password")
214 h.flash(_('Error occurred during update of user password'),
213 h.flash(_('Error occurred during update of user password'),
215 category='error')
214 category='error')
216 return render('admin/my_account/my_account.html')
215 return render('admin/my_account/my_account.html')
217
216
218 def my_account_password(self):
217 def my_account_password(self):
219 c.active = 'password'
218 c.active = 'password'
220 self.__load_data()
219 self.__load_data()
221 return render('admin/my_account/my_account.html')
220 return render('admin/my_account/my_account.html')
222
221
223 def my_account_repos(self):
222 def my_account_repos(self):
224 c.active = 'repos'
223 c.active = 'repos'
225 self.__load_data()
224 self.__load_data()
226
225
227 # json used to render the grid
226 # json used to render the grid
228 c.data = self._load_my_repos_data()
227 c.data = self._load_my_repos_data()
229 return render('admin/my_account/my_account.html')
228 return render('admin/my_account/my_account.html')
230
229
231 def my_account_watched(self):
230 def my_account_watched(self):
232 c.active = 'watched'
231 c.active = 'watched'
233 self.__load_data()
232 self.__load_data()
234
233
235 # json used to render the grid
234 # json used to render the grid
236 c.data = self._load_my_repos_data(watched=True)
235 c.data = self._load_my_repos_data(watched=True)
237 return render('admin/my_account/my_account.html')
236 return render('admin/my_account/my_account.html')
238
237
239 def my_account_perms(self):
238 def my_account_perms(self):
240 c.active = 'perms'
239 c.active = 'perms'
241 self.__load_data()
240 self.__load_data()
242 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
241 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
243 ip_addr=self.ip_addr)
242 ip_addr=self.ip_addr)
244
243
245 return render('admin/my_account/my_account.html')
244 return render('admin/my_account/my_account.html')
246
245
247 def my_account_emails(self):
246 def my_account_emails(self):
248 c.active = 'emails'
247 c.active = 'emails'
249 self.__load_data()
248 self.__load_data()
250
249
251 c.user_email_map = UserEmailMap.query()\
250 c.user_email_map = UserEmailMap.query()\
252 .filter(UserEmailMap.user == c.user).all()
251 .filter(UserEmailMap.user == c.user).all()
253 return render('admin/my_account/my_account.html')
252 return render('admin/my_account/my_account.html')
254
253
255 @auth.CSRFRequired()
254 @auth.CSRFRequired()
256 def my_account_emails_add(self):
255 def my_account_emails_add(self):
257 email = request.POST.get('new_email')
256 email = request.POST.get('new_email')
258
257
259 try:
258 try:
260 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
259 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
261 Session().commit()
260 Session().commit()
262 h.flash(_("Added new email address `%s` for user account") % email,
261 h.flash(_("Added new email address `%s` for user account") % email,
263 category='success')
262 category='success')
264 except formencode.Invalid as error:
263 except formencode.Invalid as error:
265 msg = error.error_dict['email']
264 msg = error.error_dict['email']
266 h.flash(msg, category='error')
265 h.flash(msg, category='error')
267 except Exception:
266 except Exception:
268 log.exception("Exception in my_account_emails")
267 log.exception("Exception in my_account_emails")
269 h.flash(_('An error occurred during email saving'),
268 h.flash(_('An error occurred during email saving'),
270 category='error')
269 category='error')
271 return redirect(url('my_account_emails'))
270 return redirect(url('my_account_emails'))
272
271
273 @auth.CSRFRequired()
272 @auth.CSRFRequired()
274 def my_account_emails_delete(self):
273 def my_account_emails_delete(self):
275 email_id = request.POST.get('del_email_id')
274 email_id = request.POST.get('del_email_id')
276 user_model = UserModel()
275 user_model = UserModel()
277 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
276 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
278 Session().commit()
277 Session().commit()
279 h.flash(_("Removed email address from user account"),
278 h.flash(_("Removed email address from user account"),
280 category='success')
279 category='success')
281 return redirect(url('my_account_emails'))
280 return redirect(url('my_account_emails'))
282
281
283 def my_account_pullrequests(self):
282 def my_account_pullrequests(self):
284 c.active = 'pullrequests'
283 c.active = 'pullrequests'
285 self.__load_data()
284 self.__load_data()
286 c.show_closed = request.GET.get('pr_show_closed')
285 c.show_closed = request.GET.get('pr_show_closed')
287
286
288 def _filter(pr):
287 def _filter(pr):
289 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
288 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
290 if not c.show_closed:
289 if not c.show_closed:
291 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
290 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
292 return s
291 return s
293
292
294 c.my_pull_requests = _filter(
293 c.my_pull_requests = _filter(
295 PullRequest.query().filter(
294 PullRequest.query().filter(
296 PullRequest.user_id == c.rhodecode_user.user_id).all())
295 PullRequest.user_id == c.rhodecode_user.user_id).all())
297 my_prs = [
296 my_prs = [
298 x.pull_request for x in PullRequestReviewers.query().filter(
297 x.pull_request for x in PullRequestReviewers.query().filter(
299 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
298 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
300 c.participate_in_pull_requests = _filter(my_prs)
299 c.participate_in_pull_requests = _filter(my_prs)
301 return render('admin/my_account/my_account.html')
300 return render('admin/my_account/my_account.html')
302
301
303 def my_account_auth_tokens(self):
302 def my_account_auth_tokens(self):
304 c.active = 'auth_tokens'
303 c.active = 'auth_tokens'
305 self.__load_data()
304 self.__load_data()
306 show_expired = True
305 show_expired = True
307 c.lifetime_values = [
306 c.lifetime_values = [
308 (str(-1), _('forever')),
307 (str(-1), _('forever')),
309 (str(5), _('5 minutes')),
308 (str(5), _('5 minutes')),
310 (str(60), _('1 hour')),
309 (str(60), _('1 hour')),
311 (str(60 * 24), _('1 day')),
310 (str(60 * 24), _('1 day')),
312 (str(60 * 24 * 30), _('1 month')),
311 (str(60 * 24 * 30), _('1 month')),
313 ]
312 ]
314 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
313 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
315 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
314 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
316 for x in AuthTokenModel.cls.ROLES]
315 for x in AuthTokenModel.cls.ROLES]
317 c.role_options = [(c.role_values, _("Role"))]
316 c.role_options = [(c.role_values, _("Role"))]
318 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
317 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
319 c.rhodecode_user.user_id, show_expired=show_expired)
318 c.rhodecode_user.user_id, show_expired=show_expired)
320 return render('admin/my_account/my_account.html')
319 return render('admin/my_account/my_account.html')
321
320
322 @auth.CSRFRequired()
321 @auth.CSRFRequired()
323 def my_account_auth_tokens_add(self):
322 def my_account_auth_tokens_add(self):
324 lifetime = safe_int(request.POST.get('lifetime'), -1)
323 lifetime = safe_int(request.POST.get('lifetime'), -1)
325 description = request.POST.get('description')
324 description = request.POST.get('description')
326 role = request.POST.get('role')
325 role = request.POST.get('role')
327 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
326 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
328 role)
327 role)
329 Session().commit()
328 Session().commit()
330 h.flash(_("Auth token successfully created"), category='success')
329 h.flash(_("Auth token successfully created"), category='success')
331 return redirect(url('my_account_auth_tokens'))
330 return redirect(url('my_account_auth_tokens'))
332
331
333 @auth.CSRFRequired()
332 @auth.CSRFRequired()
334 def my_account_auth_tokens_delete(self):
333 def my_account_auth_tokens_delete(self):
335 auth_token = request.POST.get('del_auth_token')
334 auth_token = request.POST.get('del_auth_token')
336 user_id = c.rhodecode_user.user_id
335 user_id = c.rhodecode_user.user_id
337 if request.POST.get('del_auth_token_builtin'):
336 if request.POST.get('del_auth_token_builtin'):
338 user = User.get(user_id)
337 user = User.get(user_id)
339 if user:
338 if user:
340 user.api_key = generate_auth_token(user.username)
339 user.api_key = generate_auth_token(user.username)
341 Session().add(user)
340 Session().add(user)
342 Session().commit()
341 Session().commit()
343 h.flash(_("Auth token successfully reset"), category='success')
342 h.flash(_("Auth token successfully reset"), category='success')
344 elif auth_token:
343 elif auth_token:
345 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
344 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
346 Session().commit()
345 Session().commit()
347 h.flash(_("Auth token successfully deleted"), category='success')
346 h.flash(_("Auth token successfully deleted"), category='success')
348
347
349 return redirect(url('my_account_auth_tokens'))
348 return redirect(url('my_account_auth_tokens'))
350
351 def my_account_oauth(self):
352 c.active = 'oauth'
353 self.__load_data()
354 c.user_oauth_tokens = ExternalIdentity().by_local_user_id(
355 c.rhodecode_user.user_id).all()
356 settings = SettingsModel().get_all_settings()
357 c.social_plugins = SettingsModel().list_enabled_social_plugins(
358 settings)
359 return render('admin/my_account/my_account.html')
360
361 @auth.CSRFRequired()
362 def my_account_oauth_delete(self):
363 token = ExternalIdentity.by_external_id_and_provider(
364 request.params.get('external_id'),
365 request.params.get('provider_name'),
366 local_user_id=c.rhodecode_user.user_id
367 )
368 if token:
369 Session().delete(token)
370 Session().commit()
371 h.flash(_("OAuth token successfully deleted"), category='success')
372
373 return redirect(url('my_account_oauth'))
@@ -1,471 +1,480 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 User Groups crud controller for pylons
22 User Groups crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 from formencode import htmlfill
28 from formencode import htmlfill
29 from pylons import request, tmpl_context as c, url, config
29 from pylons import request, tmpl_context as c, url, config
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34
34
35 from rhodecode.lib import auth
35 from rhodecode.lib import auth
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
38 RepoGroupAssignmentError
38 RepoGroupAssignmentError
39 from rhodecode.lib.utils import jsonify, action_logger
39 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
40 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
40 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
41 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
42 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
42 HasPermissionAnyDecorator)
43 HasPermissionAnyDecorator)
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
44 from rhodecode.model.permission import PermissionModel
45 from rhodecode.model.permission import PermissionModel
45 from rhodecode.model.scm import UserGroupList
46 from rhodecode.model.scm import UserGroupList
46 from rhodecode.model.user_group import UserGroupModel
47 from rhodecode.model.user_group import UserGroupModel
47 from rhodecode.model.db import (
48 from rhodecode.model.db import (
48 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
49 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
49 from rhodecode.model.forms import (
50 from rhodecode.model.forms import (
50 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
51 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
51 UserPermissionsForm)
52 UserPermissionsForm)
52 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
53 from rhodecode.lib.utils import action_logger
54 from rhodecode.lib.utils import action_logger
54 from rhodecode.lib.ext_json import json
55 from rhodecode.lib.ext_json import json
55
56
56 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
57
58
58
59
59 class UserGroupsController(BaseController):
60 class UserGroupsController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
61 """REST Controller styled on the Atom Publishing Protocol"""
61
62
62 @LoginRequired()
63 @LoginRequired()
63 def __before__(self):
64 def __before__(self):
64 super(UserGroupsController, self).__before__()
65 super(UserGroupsController, self).__before__()
65 c.available_permissions = config['available_permissions']
66 c.available_permissions = config['available_permissions']
66 PermissionModel().set_global_permission_choices(c, translator=_)
67 PermissionModel().set_global_permission_choices(c, translator=_)
67
68
68 def __load_data(self, user_group_id):
69 def __load_data(self, user_group_id):
69 c.group_members_obj = [x.user for x in c.user_group.members]
70 c.group_members_obj = [x.user for x in c.user_group.members]
70 c.group_members_obj.sort(key=lambda u: u.username.lower())
71 c.group_members_obj.sort(key=lambda u: u.username.lower())
71
72
72 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
73
74
74 c.available_members = [(x.user_id, x.username)
75 c.available_members = [(x.user_id, x.username)
75 for x in User.query().all()]
76 for x in User.query().all()]
76 c.available_members.sort(key=lambda u: u[1].lower())
77 c.available_members.sort(key=lambda u: u[1].lower())
77
78
78 def __load_defaults(self, user_group_id):
79 def __load_defaults(self, user_group_id):
79 """
80 """
80 Load defaults settings for edit, and update
81 Load defaults settings for edit, and update
81
82
82 :param user_group_id:
83 :param user_group_id:
83 """
84 """
84 user_group = UserGroup.get_or_404(user_group_id)
85 user_group = UserGroup.get_or_404(user_group_id)
85 data = user_group.get_dict()
86 data = user_group.get_dict()
86 # fill owner
87 # fill owner
87 if user_group.user:
88 if user_group.user:
88 data.update({'user': user_group.user.username})
89 data.update({'user': user_group.user.username})
89 else:
90 else:
90 replacement_user = User.get_first_admin().username
91 replacement_user = User.get_first_admin().username
91 data.update({'user': replacement_user})
92 data.update({'user': replacement_user})
92 return data
93 return data
93
94
94 def _revoke_perms_on_yourself(self, form_result):
95 def _revoke_perms_on_yourself(self, form_result):
95 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 form_result['perm_updates'])
97 form_result['perm_updates'])
97 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 form_result['perm_additions'])
99 form_result['perm_additions'])
99 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
100 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
100 form_result['perm_deletions'])
101 form_result['perm_deletions'])
101 admin_perm = 'usergroup.admin'
102 admin_perm = 'usergroup.admin'
102 if _updates and _updates[0][1] != admin_perm or \
103 if _updates and _updates[0][1] != admin_perm or \
103 _additions and _additions[0][1] != admin_perm or \
104 _additions and _additions[0][1] != admin_perm or \
104 _deletions and _deletions[0][1] != admin_perm:
105 _deletions and _deletions[0][1] != admin_perm:
105 return True
106 return True
106 return False
107 return False
107
108
108 # permission check inside
109 # permission check inside
109 @NotAnonymous()
110 @NotAnonymous()
110 def index(self):
111 def index(self):
111 """GET /users_groups: All items in the collection"""
112 """GET /users_groups: All items in the collection"""
112 # url('users_groups')
113 # url('users_groups')
113
114
114 from rhodecode.lib.utils import PartialRenderer
115 from rhodecode.lib.utils import PartialRenderer
115 _render = PartialRenderer('data_table/_dt_elements.html')
116 _render = PartialRenderer('data_table/_dt_elements.html')
116
117
117 def user_group_name(user_group_id, user_group_name):
118 def user_group_name(user_group_id, user_group_name):
118 return _render("user_group_name", user_group_id, user_group_name)
119 return _render("user_group_name", user_group_id, user_group_name)
119
120
120 def user_group_actions(user_group_id, user_group_name):
121 def user_group_actions(user_group_id, user_group_name):
121 return _render("user_group_actions", user_group_id, user_group_name)
122 return _render("user_group_actions", user_group_id, user_group_name)
122
123
123 ## json generate
124 ## json generate
124 group_iter = UserGroupList(UserGroup.query().all(),
125 group_iter = UserGroupList(UserGroup.query().all(),
125 perm_set=['usergroup.admin'])
126 perm_set=['usergroup.admin'])
126
127
127 user_groups_data = []
128 user_groups_data = []
128 for user_gr in group_iter:
129 for user_gr in group_iter:
129 user_groups_data.append({
130 user_groups_data.append({
130 "group_name": user_group_name(
131 "group_name": user_group_name(
131 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
132 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
132 "group_name_raw": user_gr.users_group_name,
133 "group_name_raw": user_gr.users_group_name,
133 "desc": h.escape(user_gr.user_group_description),
134 "desc": h.escape(user_gr.user_group_description),
134 "members": len(user_gr.members),
135 "members": len(user_gr.members),
135 "active": h.bool2icon(user_gr.users_group_active),
136 "active": h.bool2icon(user_gr.users_group_active),
136 "owner": h.escape(h.link_to_user(user_gr.user.username)),
137 "owner": h.escape(h.link_to_user(user_gr.user.username)),
137 "action": user_group_actions(
138 "action": user_group_actions(
138 user_gr.users_group_id, user_gr.users_group_name)
139 user_gr.users_group_id, user_gr.users_group_name)
139 })
140 })
140
141
141 c.data = json.dumps(user_groups_data)
142 c.data = json.dumps(user_groups_data)
142 return render('admin/user_groups/user_groups.html')
143 return render('admin/user_groups/user_groups.html')
143
144
144 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
145 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
145 @auth.CSRFRequired()
146 @auth.CSRFRequired()
146 def create(self):
147 def create(self):
147 """POST /users_groups: Create a new item"""
148 """POST /users_groups: Create a new item"""
148 # url('users_groups')
149 # url('users_groups')
149
150
150 users_group_form = UserGroupForm()()
151 users_group_form = UserGroupForm()()
151 try:
152 try:
152 form_result = users_group_form.to_python(dict(request.POST))
153 form_result = users_group_form.to_python(dict(request.POST))
153 user_group = UserGroupModel().create(
154 user_group = UserGroupModel().create(
154 name=form_result['users_group_name'],
155 name=form_result['users_group_name'],
155 description=form_result['user_group_description'],
156 description=form_result['user_group_description'],
156 owner=c.rhodecode_user.user_id,
157 owner=c.rhodecode_user.user_id,
157 active=form_result['users_group_active'])
158 active=form_result['users_group_active'])
158 Session().flush()
159 Session().flush()
159
160
160 user_group_name = form_result['users_group_name']
161 user_group_name = form_result['users_group_name']
161 action_logger(c.rhodecode_user,
162 action_logger(c.rhodecode_user,
162 'admin_created_users_group:%s' % user_group_name,
163 'admin_created_users_group:%s' % user_group_name,
163 None, self.ip_addr, self.sa)
164 None, self.ip_addr, self.sa)
164 user_group_link = h.link_to(h.escape(user_group_name),
165 user_group_link = h.link_to(h.escape(user_group_name),
165 url('edit_users_group',
166 url('edit_users_group',
166 user_group_id=user_group.users_group_id))
167 user_group_id=user_group.users_group_id))
167 h.flash(h.literal(_('Created user group %(user_group_link)s')
168 h.flash(h.literal(_('Created user group %(user_group_link)s')
168 % {'user_group_link': user_group_link}),
169 % {'user_group_link': user_group_link}),
169 category='success')
170 category='success')
170 Session().commit()
171 Session().commit()
171 except formencode.Invalid as errors:
172 except formencode.Invalid as errors:
172 return htmlfill.render(
173 return htmlfill.render(
173 render('admin/user_groups/user_group_add.html'),
174 render('admin/user_groups/user_group_add.html'),
174 defaults=errors.value,
175 defaults=errors.value,
175 errors=errors.error_dict or {},
176 errors=errors.error_dict or {},
176 prefix_error=False,
177 prefix_error=False,
177 encoding="UTF-8",
178 encoding="UTF-8",
178 force_defaults=False)
179 force_defaults=False)
179 except Exception:
180 except Exception:
180 log.exception("Exception creating user group")
181 log.exception("Exception creating user group")
181 h.flash(_('Error occurred during creation of user group %s') \
182 h.flash(_('Error occurred during creation of user group %s') \
182 % request.POST.get('users_group_name'), category='error')
183 % request.POST.get('users_group_name'), category='error')
183
184
184 return redirect(url('users_groups'))
185 return redirect(
186 url('edit_users_group', user_group_id=user_group.users_group_id))
185
187
186 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
188 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
187 def new(self):
189 def new(self):
188 """GET /user_groups/new: Form to create a new item"""
190 """GET /user_groups/new: Form to create a new item"""
189 # url('new_users_group')
191 # url('new_users_group')
190 return render('admin/user_groups/user_group_add.html')
192 return render('admin/user_groups/user_group_add.html')
191
193
192 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
194 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
193 @auth.CSRFRequired()
195 @auth.CSRFRequired()
194 def update(self, user_group_id):
196 def update(self, user_group_id):
195 """PUT /user_groups/user_group_id: Update an existing item"""
197 """PUT /user_groups/user_group_id: Update an existing item"""
196 # Forms posted to this method should contain a hidden field:
198 # Forms posted to this method should contain a hidden field:
197 # <input type="hidden" name="_method" value="PUT" />
199 # <input type="hidden" name="_method" value="PUT" />
198 # Or using helpers:
200 # Or using helpers:
199 # h.form(url('users_group', user_group_id=ID),
201 # h.form(url('users_group', user_group_id=ID),
200 # method='put')
202 # method='put')
201 # url('users_group', user_group_id=ID)
203 # url('users_group', user_group_id=ID)
202
204
203 user_group_id = safe_int(user_group_id)
205 user_group_id = safe_int(user_group_id)
204 c.user_group = UserGroup.get_or_404(user_group_id)
206 c.user_group = UserGroup.get_or_404(user_group_id)
205 c.active = 'settings'
207 c.active = 'settings'
206 self.__load_data(user_group_id)
208 self.__load_data(user_group_id)
207
209
208 available_members = [safe_unicode(x[0]) for x in c.available_members]
210 available_members = [safe_unicode(x[0]) for x in c.available_members]
209
211
210 users_group_form = UserGroupForm(edit=True,
212 users_group_form = UserGroupForm(edit=True,
211 old_data=c.user_group.get_dict(),
213 old_data=c.user_group.get_dict(),
212 available_members=available_members)()
214 available_members=available_members)()
213
215
214 try:
216 try:
215 form_result = users_group_form.to_python(request.POST)
217 form_result = users_group_form.to_python(request.POST)
216 UserGroupModel().update(c.user_group, form_result)
218 UserGroupModel().update(c.user_group, form_result)
217 gr = form_result['users_group_name']
219 gr = form_result['users_group_name']
218 action_logger(c.rhodecode_user,
220 action_logger(c.rhodecode_user,
219 'admin_updated_users_group:%s' % gr,
221 'admin_updated_users_group:%s' % gr,
220 None, self.ip_addr, self.sa)
222 None, self.ip_addr, self.sa)
221 h.flash(_('Updated user group %s') % gr, category='success')
223 h.flash(_('Updated user group %s') % gr, category='success')
222 Session().commit()
224 Session().commit()
223 except formencode.Invalid as errors:
225 except formencode.Invalid as errors:
224 defaults = errors.value
226 defaults = errors.value
225 e = errors.error_dict or {}
227 e = errors.error_dict or {}
226
228
227 return htmlfill.render(
229 return htmlfill.render(
228 render('admin/user_groups/user_group_edit.html'),
230 render('admin/user_groups/user_group_edit.html'),
229 defaults=defaults,
231 defaults=defaults,
230 errors=e,
232 errors=e,
231 prefix_error=False,
233 prefix_error=False,
232 encoding="UTF-8",
234 encoding="UTF-8",
233 force_defaults=False)
235 force_defaults=False)
234 except Exception:
236 except Exception:
235 log.exception("Exception during update of user group")
237 log.exception("Exception during update of user group")
236 h.flash(_('Error occurred during update of user group %s')
238 h.flash(_('Error occurred during update of user group %s')
237 % request.POST.get('users_group_name'), category='error')
239 % request.POST.get('users_group_name'), category='error')
238
240
239 return redirect(url('edit_users_group', user_group_id=user_group_id))
241 return redirect(url('edit_users_group', user_group_id=user_group_id))
240
242
241 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
243 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
242 @auth.CSRFRequired()
244 @auth.CSRFRequired()
243 def delete(self, user_group_id):
245 def delete(self, user_group_id):
244 """DELETE /user_groups/user_group_id: Delete an existing item"""
246 """DELETE /user_groups/user_group_id: Delete an existing item"""
245 # Forms posted to this method should contain a hidden field:
247 # Forms posted to this method should contain a hidden field:
246 # <input type="hidden" name="_method" value="DELETE" />
248 # <input type="hidden" name="_method" value="DELETE" />
247 # Or using helpers:
249 # Or using helpers:
248 # h.form(url('users_group', user_group_id=ID),
250 # h.form(url('users_group', user_group_id=ID),
249 # method='delete')
251 # method='delete')
250 # url('users_group', user_group_id=ID)
252 # url('users_group', user_group_id=ID)
251 user_group_id = safe_int(user_group_id)
253 user_group_id = safe_int(user_group_id)
252 c.user_group = UserGroup.get_or_404(user_group_id)
254 c.user_group = UserGroup.get_or_404(user_group_id)
253 force = str2bool(request.POST.get('force'))
255 force = str2bool(request.POST.get('force'))
254
256
255 try:
257 try:
256 UserGroupModel().delete(c.user_group, force=force)
258 UserGroupModel().delete(c.user_group, force=force)
257 Session().commit()
259 Session().commit()
258 h.flash(_('Successfully deleted user group'), category='success')
260 h.flash(_('Successfully deleted user group'), category='success')
259 except UserGroupAssignedException as e:
261 except UserGroupAssignedException as e:
260 h.flash(str(e), category='error')
262 h.flash(str(e), category='error')
261 except Exception:
263 except Exception:
262 log.exception("Exception during deletion of user group")
264 log.exception("Exception during deletion of user group")
263 h.flash(_('An error occurred during deletion of user group'),
265 h.flash(_('An error occurred during deletion of user group'),
264 category='error')
266 category='error')
265 return redirect(url('users_groups'))
267 return redirect(url('users_groups'))
266
268
267 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
268 def edit(self, user_group_id):
270 def edit(self, user_group_id):
269 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
271 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
270 # url('edit_users_group', user_group_id=ID)
272 # url('edit_users_group', user_group_id=ID)
271
273
272 user_group_id = safe_int(user_group_id)
274 user_group_id = safe_int(user_group_id)
273 c.user_group = UserGroup.get_or_404(user_group_id)
275 c.user_group = UserGroup.get_or_404(user_group_id)
274 c.active = 'settings'
276 c.active = 'settings'
275 self.__load_data(user_group_id)
277 self.__load_data(user_group_id)
276
278
277 defaults = self.__load_defaults(user_group_id)
279 defaults = self.__load_defaults(user_group_id)
278
280
279 return htmlfill.render(
281 return htmlfill.render(
280 render('admin/user_groups/user_group_edit.html'),
282 render('admin/user_groups/user_group_edit.html'),
281 defaults=defaults,
283 defaults=defaults,
282 encoding="UTF-8",
284 encoding="UTF-8",
283 force_defaults=False
285 force_defaults=False
284 )
286 )
285
287
286 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
288 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
287 def edit_perms(self, user_group_id):
289 def edit_perms(self, user_group_id):
288 user_group_id = safe_int(user_group_id)
290 user_group_id = safe_int(user_group_id)
289 c.user_group = UserGroup.get_or_404(user_group_id)
291 c.user_group = UserGroup.get_or_404(user_group_id)
290 c.active = 'perms'
292 c.active = 'perms'
291
293
292 defaults = {}
294 defaults = {}
293 # fill user group users
295 # fill user group users
294 for p in c.user_group.user_user_group_to_perm:
296 for p in c.user_group.user_user_group_to_perm:
295 defaults.update({'u_perm_%s' % p.user.user_id:
297 defaults.update({'u_perm_%s' % p.user.user_id:
296 p.permission.permission_name})
298 p.permission.permission_name})
297
299
298 for p in c.user_group.user_group_user_group_to_perm:
300 for p in c.user_group.user_group_user_group_to_perm:
299 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
301 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
300 p.permission.permission_name})
302 p.permission.permission_name})
301
303
302 return htmlfill.render(
304 return htmlfill.render(
303 render('admin/user_groups/user_group_edit.html'),
305 render('admin/user_groups/user_group_edit.html'),
304 defaults=defaults,
306 defaults=defaults,
305 encoding="UTF-8",
307 encoding="UTF-8",
306 force_defaults=False
308 force_defaults=False
307 )
309 )
308
310
309 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
311 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
310 @auth.CSRFRequired()
312 @auth.CSRFRequired()
311 def update_perms(self, user_group_id):
313 def update_perms(self, user_group_id):
312 """
314 """
313 grant permission for given usergroup
315 grant permission for given usergroup
314
316
315 :param user_group_id:
317 :param user_group_id:
316 """
318 """
317 user_group_id = safe_int(user_group_id)
319 user_group_id = safe_int(user_group_id)
318 c.user_group = UserGroup.get_or_404(user_group_id)
320 c.user_group = UserGroup.get_or_404(user_group_id)
319 form = UserGroupPermsForm()().to_python(request.POST)
321 form = UserGroupPermsForm()().to_python(request.POST)
320
322
321 if not c.rhodecode_user.is_admin:
323 if not c.rhodecode_user.is_admin:
322 if self._revoke_perms_on_yourself(form):
324 if self._revoke_perms_on_yourself(form):
323 msg = _('Cannot change permission for yourself as admin')
325 msg = _('Cannot change permission for yourself as admin')
324 h.flash(msg, category='warning')
326 h.flash(msg, category='warning')
325 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
326
328
327 try:
329 try:
328 UserGroupModel().update_permissions(user_group_id,
330 UserGroupModel().update_permissions(user_group_id,
329 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
331 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
330 except RepoGroupAssignmentError:
332 except RepoGroupAssignmentError:
331 h.flash(_('Target group cannot be the same'), category='error')
333 h.flash(_('Target group cannot be the same'), category='error')
332 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
333 #TODO: implement this
335 #TODO: implement this
334 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
336 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
335 # repo_name, self.ip_addr, self.sa)
337 # repo_name, self.ip_addr, self.sa)
336 Session().commit()
338 Session().commit()
337 h.flash(_('User Group permissions updated'), category='success')
339 h.flash(_('User Group permissions updated'), category='success')
338 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
340 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
339
341
340 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
342 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
341 def edit_perms_summary(self, user_group_id):
343 def edit_perms_summary(self, user_group_id):
342 user_group_id = safe_int(user_group_id)
344 user_group_id = safe_int(user_group_id)
343 c.user_group = UserGroup.get_or_404(user_group_id)
345 c.user_group = UserGroup.get_or_404(user_group_id)
344 c.active = 'perms_summary'
346 c.active = 'perms_summary'
345 permissions = {
347 permissions = {
346 'repositories': {},
348 'repositories': {},
347 'repositories_groups': {},
349 'repositories_groups': {},
348 }
350 }
349 ugroup_repo_perms = UserGroupRepoToPerm.query()\
351 ugroup_repo_perms = UserGroupRepoToPerm.query()\
350 .options(joinedload(UserGroupRepoToPerm.permission))\
352 .options(joinedload(UserGroupRepoToPerm.permission))\
351 .options(joinedload(UserGroupRepoToPerm.repository))\
353 .options(joinedload(UserGroupRepoToPerm.repository))\
352 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
354 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
353 .all()
355 .all()
354
356
355 for gr in ugroup_repo_perms:
357 for gr in ugroup_repo_perms:
356 permissions['repositories'][gr.repository.repo_name] \
358 permissions['repositories'][gr.repository.repo_name] \
357 = gr.permission.permission_name
359 = gr.permission.permission_name
358
360
359 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
361 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
360 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
362 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
361 .options(joinedload(UserGroupRepoGroupToPerm.group))\
363 .options(joinedload(UserGroupRepoGroupToPerm.group))\
362 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
364 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
363 .all()
365 .all()
364
366
365 for gr in ugroup_group_perms:
367 for gr in ugroup_group_perms:
366 permissions['repositories_groups'][gr.group.group_name] \
368 permissions['repositories_groups'][gr.group.group_name] \
367 = gr.permission.permission_name
369 = gr.permission.permission_name
368 c.permissions = permissions
370 c.permissions = permissions
369 return render('admin/user_groups/user_group_edit.html')
371 return render('admin/user_groups/user_group_edit.html')
370
372
371 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
373 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
372 def edit_global_perms(self, user_group_id):
374 def edit_global_perms(self, user_group_id):
373 user_group_id = safe_int(user_group_id)
375 user_group_id = safe_int(user_group_id)
374 c.user_group = UserGroup.get_or_404(user_group_id)
376 c.user_group = UserGroup.get_or_404(user_group_id)
375 c.active = 'global_perms'
377 c.active = 'global_perms'
376
378
377 c.default_user = User.get_default_user()
379 c.default_user = User.get_default_user()
378 defaults = c.user_group.get_dict()
380 defaults = c.user_group.get_dict()
379 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
381 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
380 defaults.update(c.user_group.get_default_perms())
382 defaults.update(c.user_group.get_default_perms())
381
383
382 return htmlfill.render(
384 return htmlfill.render(
383 render('admin/user_groups/user_group_edit.html'),
385 render('admin/user_groups/user_group_edit.html'),
384 defaults=defaults,
386 defaults=defaults,
385 encoding="UTF-8",
387 encoding="UTF-8",
386 force_defaults=False
388 force_defaults=False
387 )
389 )
388
390
389 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
391 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
390 @auth.CSRFRequired()
392 @auth.CSRFRequired()
391 def update_global_perms(self, user_group_id):
393 def update_global_perms(self, user_group_id):
392 """PUT /users_perm/user_group_id: Update an existing item"""
394 """PUT /users_perm/user_group_id: Update an existing item"""
393 # url('users_group_perm', user_group_id=ID, method='put')
395 # url('users_group_perm', user_group_id=ID, method='put')
394 user_group_id = safe_int(user_group_id)
396 user_group_id = safe_int(user_group_id)
395 user_group = UserGroup.get_or_404(user_group_id)
397 user_group = UserGroup.get_or_404(user_group_id)
396 c.active = 'global_perms'
398 c.active = 'global_perms'
397
399
398 try:
400 try:
399 # first stage that verifies the checkbox
401 # first stage that verifies the checkbox
400 _form = UserIndividualPermissionsForm()
402 _form = UserIndividualPermissionsForm()
401 form_result = _form.to_python(dict(request.POST))
403 form_result = _form.to_python(dict(request.POST))
402 inherit_perms = form_result['inherit_default_permissions']
404 inherit_perms = form_result['inherit_default_permissions']
403 user_group.inherit_default_permissions = inherit_perms
405 user_group.inherit_default_permissions = inherit_perms
404 Session().add(user_group)
406 Session().add(user_group)
405
407
406 if not inherit_perms:
408 if not inherit_perms:
407 # only update the individual ones if we un check the flag
409 # only update the individual ones if we un check the flag
408 _form = UserPermissionsForm(
410 _form = UserPermissionsForm(
409 [x[0] for x in c.repo_create_choices],
411 [x[0] for x in c.repo_create_choices],
410 [x[0] for x in c.repo_create_on_write_choices],
412 [x[0] for x in c.repo_create_on_write_choices],
411 [x[0] for x in c.repo_group_create_choices],
413 [x[0] for x in c.repo_group_create_choices],
412 [x[0] for x in c.user_group_create_choices],
414 [x[0] for x in c.user_group_create_choices],
413 [x[0] for x in c.fork_choices],
415 [x[0] for x in c.fork_choices],
414 [x[0] for x in c.inherit_default_permission_choices])()
416 [x[0] for x in c.inherit_default_permission_choices])()
415
417
416 form_result = _form.to_python(dict(request.POST))
418 form_result = _form.to_python(dict(request.POST))
417 form_result.update({'perm_user_group_id': user_group.users_group_id})
419 form_result.update({'perm_user_group_id': user_group.users_group_id})
418
420
419 PermissionModel().update_user_group_permissions(form_result)
421 PermissionModel().update_user_group_permissions(form_result)
420
422
421 Session().commit()
423 Session().commit()
422 h.flash(_('User Group global permissions updated successfully'),
424 h.flash(_('User Group global permissions updated successfully'),
423 category='success')
425 category='success')
424
426
425 except formencode.Invalid as errors:
427 except formencode.Invalid as errors:
426 defaults = errors.value
428 defaults = errors.value
427 c.user_group = user_group
429 c.user_group = user_group
428 return htmlfill.render(
430 return htmlfill.render(
429 render('admin/user_groups/user_group_edit.html'),
431 render('admin/user_groups/user_group_edit.html'),
430 defaults=defaults,
432 defaults=defaults,
431 errors=errors.error_dict or {},
433 errors=errors.error_dict or {},
432 prefix_error=False,
434 prefix_error=False,
433 encoding="UTF-8",
435 encoding="UTF-8",
434 force_defaults=False)
436 force_defaults=False)
435
437
436 except Exception:
438 except Exception:
437 log.exception("Exception during permissions saving")
439 log.exception("Exception during permissions saving")
438 h.flash(_('An error occurred during permissions saving'),
440 h.flash(_('An error occurred during permissions saving'),
439 category='error')
441 category='error')
440
442
441 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
443 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
442
444
443 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
445 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
444 def edit_advanced(self, user_group_id):
446 def edit_advanced(self, user_group_id):
445 user_group_id = safe_int(user_group_id)
447 user_group_id = safe_int(user_group_id)
446 c.user_group = UserGroup.get_or_404(user_group_id)
448 c.user_group = UserGroup.get_or_404(user_group_id)
447 c.active = 'advanced'
449 c.active = 'advanced'
448 c.group_members_obj = sorted(
450 c.group_members_obj = sorted(
449 (x.user for x in c.user_group.members),
451 (x.user for x in c.user_group.members),
450 key=lambda u: u.username.lower())
452 key=lambda u: u.username.lower())
451
453
452 c.group_to_repos = sorted(
454 c.group_to_repos = sorted(
453 (x.repository for x in c.user_group.users_group_repo_to_perm),
455 (x.repository for x in c.user_group.users_group_repo_to_perm),
454 key=lambda u: u.repo_name.lower())
456 key=lambda u: u.repo_name.lower())
455
457
456 c.group_to_repo_groups = sorted(
458 c.group_to_repo_groups = sorted(
457 (x.group for x in c.user_group.users_group_repo_group_to_perm),
459 (x.group for x in c.user_group.users_group_repo_group_to_perm),
458 key=lambda u: u.group_name.lower())
460 key=lambda u: u.group_name.lower())
459
461
460 return render('admin/user_groups/user_group_edit.html')
462 return render('admin/user_groups/user_group_edit.html')
461
463
462 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
464 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
463 def edit_members(self, user_group_id):
465 def edit_members(self, user_group_id):
464 user_group_id = safe_int(user_group_id)
466 user_group_id = safe_int(user_group_id)
465 c.user_group = UserGroup.get_or_404(user_group_id)
467 c.user_group = UserGroup.get_or_404(user_group_id)
466 c.active = 'members'
468 c.active = 'members'
467 c.group_members_obj = sorted((x.user for x in c.user_group.members),
469 c.group_members_obj = sorted((x.user for x in c.user_group.members),
468 key=lambda u: u.username.lower())
470 key=lambda u: u.username.lower())
469
471
470 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
472 group_members = [(x.user_id, x.username) for x in c.group_members_obj]
473
474 if request.is_xhr:
475 return jsonify(lambda *a, **k: {
476 'members': group_members
477 })
478
479 c.group_members = group_members
471 return render('admin/user_groups/user_group_edit.html')
480 return render('admin/user_groups/user_group_edit.html')
@@ -1,265 +1,267 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Compare controller for showing differences between two commits/refs/tags etc.
22 Compare controller for showing differences between two commits/refs/tags etc.
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from webob.exc import HTTPBadRequest
27 from webob.exc import HTTPBadRequest
28 from pylons import request, tmpl_context as c, url
28 from pylons import request, tmpl_context as c, url
29 from pylons.controllers.util import redirect
29 from pylons.controllers.util import redirect
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
32 from rhodecode.controllers.utils import parse_path_ref, get_commit_from_ref_name
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import diffs
34 from rhodecode.lib import diffs
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.utils import safe_str
37 from rhodecode.lib.utils import safe_str
38 from rhodecode.lib.utils2 import safe_unicode, str2bool
38 from rhodecode.lib.utils2 import safe_unicode, str2bool
39 from rhodecode.lib.vcs.exceptions import (
39 from rhodecode.lib.vcs.exceptions import (
40 EmptyRepositoryError, RepositoryError, RepositoryRequirementError)
40 EmptyRepositoryError, RepositoryError, RepositoryRequirementError)
41 from rhodecode.model.db import Repository, ChangesetStatus
41 from rhodecode.model.db import Repository, ChangesetStatus
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class CompareController(BaseRepoController):
46 class CompareController(BaseRepoController):
47
47
48 def __before__(self):
48 def __before__(self):
49 super(CompareController, self).__before__()
49 super(CompareController, self).__before__()
50
50
51 def _get_commit_or_redirect(
51 def _get_commit_or_redirect(
52 self, ref, ref_type, repo, redirect_after=True, partial=False):
52 self, ref, ref_type, repo, redirect_after=True, partial=False):
53 """
53 """
54 This is a safe way to get a commit. If an error occurs it
54 This is a safe way to get a commit. If an error occurs it
55 redirects to a commit with a proper message. If partial is set
55 redirects to a commit with a proper message. If partial is set
56 then it does not do redirect raise and throws an exception instead.
56 then it does not do redirect raise and throws an exception instead.
57 """
57 """
58 try:
58 try:
59 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
59 return get_commit_from_ref_name(repo, safe_str(ref), ref_type)
60 except EmptyRepositoryError:
60 except EmptyRepositoryError:
61 if not redirect_after:
61 if not redirect_after:
62 return repo.scm_instance().EMPTY_COMMIT
62 return repo.scm_instance().EMPTY_COMMIT
63 h.flash(h.literal(_('There are no commits yet')),
63 h.flash(h.literal(_('There are no commits yet')),
64 category='warning')
64 category='warning')
65 redirect(url('summary_home', repo_name=repo.repo_name))
65 redirect(url('summary_home', repo_name=repo.repo_name))
66
66
67 except RepositoryError as e:
67 except RepositoryError as e:
68 msg = safe_str(e)
68 msg = safe_str(e)
69 log.exception(msg)
69 log.exception(msg)
70 h.flash(msg, category='warning')
70 h.flash(msg, category='warning')
71 if not partial:
71 if not partial:
72 redirect(h.url('summary_home', repo_name=repo.repo_name))
72 redirect(h.url('summary_home', repo_name=repo.repo_name))
73 raise HTTPBadRequest()
73 raise HTTPBadRequest()
74
74
75 @LoginRequired()
75 @LoginRequired()
76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
76 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
77 'repository.admin')
77 'repository.admin')
78 def index(self, repo_name):
78 def index(self, repo_name):
79 c.compare_home = True
79 c.compare_home = True
80 c.commit_ranges = []
80 c.commit_ranges = []
81 c.files = []
81 c.files = []
82 c.limited_diff = False
82 c.limited_diff = False
83 source_repo = c.rhodecode_db_repo.repo_name
83 source_repo = c.rhodecode_db_repo.repo_name
84 target_repo = request.GET.get('target_repo', source_repo)
84 target_repo = request.GET.get('target_repo', source_repo)
85 c.source_repo = Repository.get_by_repo_name(source_repo)
85 c.source_repo = Repository.get_by_repo_name(source_repo)
86 c.target_repo = Repository.get_by_repo_name(target_repo)
86 c.target_repo = Repository.get_by_repo_name(target_repo)
87 c.source_ref = c.target_ref = _('Select commit')
87 c.source_ref = c.target_ref = _('Select commit')
88 c.source_ref_type = ""
88 c.source_ref_type = ""
89 c.target_ref_type = ""
89 c.target_ref_type = ""
90 c.commit_statuses = ChangesetStatus.STATUSES
90 c.commit_statuses = ChangesetStatus.STATUSES
91 c.preview_mode = False
91 c.preview_mode = False
92 return render('compare/compare_diff.html')
92 return render('compare/compare_diff.html')
93
93
94 @LoginRequired()
94 @LoginRequired()
95 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
95 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
96 'repository.admin')
96 'repository.admin')
97 def compare(self, repo_name, source_ref_type, source_ref,
97 def compare(self, repo_name, source_ref_type, source_ref,
98 target_ref_type, target_ref):
98 target_ref_type, target_ref):
99 # source_ref will be evaluated in source_repo
99 # source_ref will be evaluated in source_repo
100 source_repo_name = c.rhodecode_db_repo.repo_name
100 source_repo_name = c.rhodecode_db_repo.repo_name
101 source_path, source_id = parse_path_ref(source_ref)
101 source_path, source_id = parse_path_ref(source_ref)
102
102
103 # target_ref will be evaluated in target_repo
103 # target_ref will be evaluated in target_repo
104 target_repo_name = request.GET.get('target_repo', source_repo_name)
104 target_repo_name = request.GET.get('target_repo', source_repo_name)
105 target_path, target_id = parse_path_ref(target_ref)
105 target_path, target_id = parse_path_ref(target_ref)
106
106
107 c.commit_statuses = ChangesetStatus.STATUSES
107 c.commit_statuses = ChangesetStatus.STATUSES
108
108
109 # if merge is True
109 # if merge is True
110 # Show what changes since the shared ancestor commit of target/source
110 # Show what changes since the shared ancestor commit of target/source
111 # the source would get if it was merged with target. Only commits
111 # the source would get if it was merged with target. Only commits
112 # which are in target but not in source will be shown.
112 # which are in target but not in source will be shown.
113 merge = str2bool(request.GET.get('merge'))
113 merge = str2bool(request.GET.get('merge'))
114 # if merge is False
114 # if merge is False
115 # Show a raw diff of source/target refs even if no ancestor exists
115 # Show a raw diff of source/target refs even if no ancestor exists
116
116
117
117
118 # c.fulldiff disables cut_off_limit
118 # c.fulldiff disables cut_off_limit
119 c.fulldiff = str2bool(request.GET.get('fulldiff'))
119 c.fulldiff = str2bool(request.GET.get('fulldiff'))
120
120
121 # if partial, returns just compare_commits.html (commits log)
121 # if partial, returns just compare_commits.html (commits log)
122 partial = request.is_xhr
122 partial = request.is_xhr
123
123
124 # swap url for compare_diff page
124 # swap url for compare_diff page
125 c.swap_url = h.url(
125 c.swap_url = h.url(
126 'compare_url',
126 'compare_url',
127 repo_name=target_repo_name,
127 repo_name=target_repo_name,
128 source_ref_type=target_ref_type,
128 source_ref_type=target_ref_type,
129 source_ref=target_ref,
129 source_ref=target_ref,
130 target_repo=source_repo_name,
130 target_repo=source_repo_name,
131 target_ref_type=source_ref_type,
131 target_ref_type=source_ref_type,
132 target_ref=source_ref,
132 target_ref=source_ref,
133 merge=merge and '1' or '')
133 merge=merge and '1' or '')
134
134
135 source_repo = Repository.get_by_repo_name(source_repo_name)
135 source_repo = Repository.get_by_repo_name(source_repo_name)
136 target_repo = Repository.get_by_repo_name(target_repo_name)
136 target_repo = Repository.get_by_repo_name(target_repo_name)
137
137
138 if source_repo is None:
138 if source_repo is None:
139 msg = _('Could not find the original repo: %(repo)s') % {
139 msg = _('Could not find the original repo: %(repo)s') % {
140 'repo': source_repo}
140 'repo': source_repo}
141
141
142 log.error(msg)
142 log.error(msg)
143 h.flash(msg, category='error')
143 h.flash(msg, category='error')
144 return redirect(url('compare_home', repo_name=c.repo_name))
144 return redirect(url('compare_home', repo_name=c.repo_name))
145
145
146 if target_repo is None:
146 if target_repo is None:
147 msg = _('Could not find the other repo: %(repo)s') % {
147 msg = _('Could not find the other repo: %(repo)s') % {
148 'repo': target_repo_name}
148 'repo': target_repo_name}
149 log.error(msg)
149 log.error(msg)
150 h.flash(msg, category='error')
150 h.flash(msg, category='error')
151 return redirect(url('compare_home', repo_name=c.repo_name))
151 return redirect(url('compare_home', repo_name=c.repo_name))
152
152
153 source_alias = source_repo.scm_instance().alias
153 source_alias = source_repo.scm_instance().alias
154 target_alias = target_repo.scm_instance().alias
154 target_alias = target_repo.scm_instance().alias
155 if source_alias != target_alias:
155 if source_alias != target_alias:
156 msg = _('The comparison of two different kinds of remote repos '
156 msg = _('The comparison of two different kinds of remote repos '
157 'is not available')
157 'is not available')
158 log.error(msg)
158 log.error(msg)
159 h.flash(msg, category='error')
159 h.flash(msg, category='error')
160 return redirect(url('compare_home', repo_name=c.repo_name))
160 return redirect(url('compare_home', repo_name=c.repo_name))
161
161
162 source_commit = self._get_commit_or_redirect(
162 source_commit = self._get_commit_or_redirect(
163 ref=source_id, ref_type=source_ref_type, repo=source_repo,
163 ref=source_id, ref_type=source_ref_type, repo=source_repo,
164 partial=partial)
164 partial=partial)
165 target_commit = self._get_commit_or_redirect(
165 target_commit = self._get_commit_or_redirect(
166 ref=target_id, ref_type=target_ref_type, repo=target_repo,
166 ref=target_id, ref_type=target_ref_type, repo=target_repo,
167 partial=partial)
167 partial=partial)
168
168
169 c.compare_home = False
169 c.compare_home = False
170 c.source_repo = source_repo
170 c.source_repo = source_repo
171 c.target_repo = target_repo
171 c.target_repo = target_repo
172 c.source_ref = source_ref
172 c.source_ref = source_ref
173 c.target_ref = target_ref
173 c.target_ref = target_ref
174 c.source_ref_type = source_ref_type
174 c.source_ref_type = source_ref_type
175 c.target_ref_type = target_ref_type
175 c.target_ref_type = target_ref_type
176
176
177 source_scm = source_repo.scm_instance()
177 source_scm = source_repo.scm_instance()
178 target_scm = target_repo.scm_instance()
178 target_scm = target_repo.scm_instance()
179
179
180 pre_load = ["author", "branch", "date", "message"]
180 pre_load = ["author", "branch", "date", "message"]
181 c.ancestor = None
181 c.ancestor = None
182 try:
182 try:
183 c.commit_ranges = source_scm.compare(
183 c.commit_ranges = source_scm.compare(
184 source_commit.raw_id, target_commit.raw_id,
184 source_commit.raw_id, target_commit.raw_id,
185 target_scm, merge, pre_load=pre_load)
185 target_scm, merge, pre_load=pre_load)
186 if merge:
186 if merge:
187 c.ancestor = source_scm.get_common_ancestor(
187 c.ancestor = source_scm.get_common_ancestor(
188 source_commit.raw_id, target_commit.raw_id, target_scm)
188 source_commit.raw_id, target_commit.raw_id, target_scm)
189 except RepositoryRequirementError:
189 except RepositoryRequirementError:
190 msg = _('Could not compare repos with different '
190 msg = _('Could not compare repos with different '
191 'large file settings')
191 'large file settings')
192 log.error(msg)
192 log.error(msg)
193 if partial:
193 if partial:
194 return msg
194 return msg
195 h.flash(msg, category='error')
195 h.flash(msg, category='error')
196 return redirect(url('compare_home', repo_name=c.repo_name))
196 return redirect(url('compare_home', repo_name=c.repo_name))
197
197
198 c.statuses = c.rhodecode_db_repo.statuses(
198 c.statuses = c.rhodecode_db_repo.statuses(
199 [x.raw_id for x in c.commit_ranges])
199 [x.raw_id for x in c.commit_ranges])
200
200
201 if partial:
201 if partial: # for PR ajax commits loader
202 if not c.ancestor:
203 return '' # cannot merge if there is no ancestor
202 return render('compare/compare_commits.html')
204 return render('compare/compare_commits.html')
203
205
204 if c.ancestor:
206 if c.ancestor:
205 # case we want a simple diff without incoming commits,
207 # case we want a simple diff without incoming commits,
206 # previewing what will be merged.
208 # previewing what will be merged.
207 # Make the diff on target repo (which is known to have target_ref)
209 # Make the diff on target repo (which is known to have target_ref)
208 log.debug('Using ancestor %s as source_ref instead of %s'
210 log.debug('Using ancestor %s as source_ref instead of %s'
209 % (c.ancestor, source_ref))
211 % (c.ancestor, source_ref))
210 source_repo = target_repo
212 source_repo = target_repo
211 source_commit = target_repo.get_commit(commit_id=c.ancestor)
213 source_commit = target_repo.get_commit(commit_id=c.ancestor)
212
214
213 # diff_limit will cut off the whole diff if the limit is applied
215 # diff_limit will cut off the whole diff if the limit is applied
214 # otherwise it will just hide the big files from the front-end
216 # otherwise it will just hide the big files from the front-end
215 diff_limit = self.cut_off_limit_diff
217 diff_limit = self.cut_off_limit_diff
216 file_limit = self.cut_off_limit_file
218 file_limit = self.cut_off_limit_file
217
219
218 log.debug('calculating diff between '
220 log.debug('calculating diff between '
219 'source_ref:%s and target_ref:%s for repo `%s`',
221 'source_ref:%s and target_ref:%s for repo `%s`',
220 source_commit, target_commit,
222 source_commit, target_commit,
221 safe_unicode(source_repo.scm_instance().path))
223 safe_unicode(source_repo.scm_instance().path))
222
224
223 if source_commit.repository != target_commit.repository:
225 if source_commit.repository != target_commit.repository:
224 msg = _(
226 msg = _(
225 "Repositories unrelated. "
227 "Repositories unrelated. "
226 "Cannot compare commit %(commit1)s from repository %(repo1)s "
228 "Cannot compare commit %(commit1)s from repository %(repo1)s "
227 "with commit %(commit2)s from repository %(repo2)s.") % {
229 "with commit %(commit2)s from repository %(repo2)s.") % {
228 'commit1': h.show_id(source_commit),
230 'commit1': h.show_id(source_commit),
229 'repo1': source_repo.repo_name,
231 'repo1': source_repo.repo_name,
230 'commit2': h.show_id(target_commit),
232 'commit2': h.show_id(target_commit),
231 'repo2': target_repo.repo_name,
233 'repo2': target_repo.repo_name,
232 }
234 }
233 h.flash(msg, category='error')
235 h.flash(msg, category='error')
234 raise HTTPBadRequest()
236 raise HTTPBadRequest()
235
237
236 txtdiff = source_repo.scm_instance().get_diff(
238 txtdiff = source_repo.scm_instance().get_diff(
237 commit1=source_commit, commit2=target_commit,
239 commit1=source_commit, commit2=target_commit,
238 path1=source_path, path=target_path)
240 path1=source_path, path=target_path)
239 diff_processor = diffs.DiffProcessor(
241 diff_processor = diffs.DiffProcessor(
240 txtdiff, format='gitdiff', diff_limit=diff_limit,
242 txtdiff, format='gitdiff', diff_limit=diff_limit,
241 file_limit=file_limit, show_full_diff=c.fulldiff)
243 file_limit=file_limit, show_full_diff=c.fulldiff)
242 _parsed = diff_processor.prepare()
244 _parsed = diff_processor.prepare()
243
245
244 c.limited_diff = False
246 c.limited_diff = False
245 if isinstance(_parsed, diffs.LimitedDiffContainer):
247 if isinstance(_parsed, diffs.LimitedDiffContainer):
246 c.limited_diff = True
248 c.limited_diff = True
247
249
248 c.files = []
250 c.files = []
249 c.changes = {}
251 c.changes = {}
250 c.lines_added = 0
252 c.lines_added = 0
251 c.lines_deleted = 0
253 c.lines_deleted = 0
252 for f in _parsed:
254 for f in _parsed:
253 st = f['stats']
255 st = f['stats']
254 if not st['binary']:
256 if not st['binary']:
255 c.lines_added += st['added']
257 c.lines_added += st['added']
256 c.lines_deleted += st['deleted']
258 c.lines_deleted += st['deleted']
257 fid = h.FID('', f['filename'])
259 fid = h.FID('', f['filename'])
258 c.files.append([fid, f['operation'], f['filename'], f['stats'], f])
260 c.files.append([fid, f['operation'], f['filename'], f['stats'], f])
259 htmldiff = diff_processor.as_html(
261 htmldiff = diff_processor.as_html(
260 enable_comments=False, parsed_lines=[f])
262 enable_comments=False, parsed_lines=[f])
261 c.changes[fid] = [f['operation'], f['filename'], htmldiff, f]
263 c.changes[fid] = [f['operation'], f['filename'], htmldiff, f]
262
264
263 c.preview_mode = merge
265 c.preview_mode = merge
264
266
265 return render('compare/compare_diff.html')
267 return render('compare/compare_diff.html')
@@ -1,232 +1,277 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Home controller for RhodeCode Enterprise
22 Home controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import logging
25 import logging
26 import time
26 import time
27
27 import re
28
28
29 from pylons import tmpl_context as c, request
29 from pylons import tmpl_context as c, request, url, config
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from sqlalchemy.sql import func
31 from sqlalchemy.sql import func
32
32
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator,
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
36 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.index import searcher_from_config
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.utils import jsonify
39 from rhodecode.lib.utils import jsonify
39 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.model.db import Repository, RepoGroup
41 from rhodecode.model.db import Repository, RepoGroup
41 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.scm import RepoList, RepoGroupList
44 from rhodecode.model.scm import RepoList, RepoGroupList
44
45
45
46
46 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
47
48
48
49
49 class HomeController(BaseController):
50 class HomeController(BaseController):
50 def __before__(self):
51 def __before__(self):
51 super(HomeController, self).__before__()
52 super(HomeController, self).__before__()
52
53
53 def ping(self):
54 def ping(self):
54 """
55 """
55 Ping, doesn't require login, good for checking out the platform
56 Ping, doesn't require login, good for checking out the platform
56 """
57 """
57 instance_id = getattr(c, 'rhodecode_instanceid', '')
58 instance_id = getattr(c, 'rhodecode_instanceid', '')
58 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
59 return 'pong[%s] => %s' % (instance_id, self.ip_addr,)
59
60
60 @LoginRequired()
61 @LoginRequired()
61 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
62 def error_test(self):
63 def error_test(self):
63 """
64 """
64 Test exception handling and emails on errors
65 Test exception handling and emails on errors
65 """
66 """
66 class TestException(Exception):
67 class TestException(Exception):
67 pass
68 pass
68
69
69 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
70 msg = ('RhodeCode Enterprise %s test exception. Generation time: %s'
70 % (c.rhodecode_name, time.time()))
71 % (c.rhodecode_name, time.time()))
71 raise TestException(msg)
72 raise TestException(msg)
72
73
73 def _get_groups_and_repos(self, repo_group_id=None):
74 def _get_groups_and_repos(self, repo_group_id=None):
74 # repo groups groups
75 # repo groups groups
75 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
76 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
76 _perms = ['group.read', 'group.write', 'group.admin']
77 _perms = ['group.read', 'group.write', 'group.admin']
77 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
78 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
78 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
79 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
79 repo_group_list=repo_group_list_acl, admin=False)
80 repo_group_list=repo_group_list_acl, admin=False)
80
81
81 # repositories
82 # repositories
82 repo_list = Repository.get_all_repos(group_id=repo_group_id)
83 repo_list = Repository.get_all_repos(group_id=repo_group_id)
83 _perms = ['repository.read', 'repository.write', 'repository.admin']
84 _perms = ['repository.read', 'repository.write', 'repository.admin']
84 repo_list_acl = RepoList(repo_list, perm_set=_perms)
85 repo_list_acl = RepoList(repo_list, perm_set=_perms)
85 repo_data = RepoModel().get_repos_as_dict(
86 repo_data = RepoModel().get_repos_as_dict(
86 repo_list=repo_list_acl, admin=False)
87 repo_list=repo_list_acl, admin=False)
87
88
88 return repo_data, repo_group_data
89 return repo_data, repo_group_data
89
90
90 @LoginRequired()
91 @LoginRequired()
91 def index(self):
92 def index(self):
92 c.repo_group = None
93 c.repo_group = None
93
94
94 repo_data, repo_group_data = self._get_groups_and_repos()
95 repo_data, repo_group_data = self._get_groups_and_repos()
95 # json used to render the grids
96 # json used to render the grids
96 c.repos_data = json.dumps(repo_data)
97 c.repos_data = json.dumps(repo_data)
97 c.repo_groups_data = json.dumps(repo_group_data)
98 c.repo_groups_data = json.dumps(repo_group_data)
98
99
99 return render('/index.html')
100 return render('/index.html')
100
101
101 @LoginRequired()
102 @LoginRequired()
102 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
103 @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
103 'group.admin')
104 'group.admin')
104 def index_repo_group(self, group_name):
105 def index_repo_group(self, group_name):
105 """GET /repo_group_name: Show a specific item"""
106 """GET /repo_group_name: Show a specific item"""
106 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
107 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
107 repo_data, repo_group_data = self._get_groups_and_repos(
108 repo_data, repo_group_data = self._get_groups_and_repos(
108 c.repo_group.group_id)
109 c.repo_group.group_id)
109
110
110 # json used to render the grids
111 # json used to render the grids
111 c.repos_data = json.dumps(repo_data)
112 c.repos_data = json.dumps(repo_data)
112 c.repo_groups_data = json.dumps(repo_group_data)
113 c.repo_groups_data = json.dumps(repo_group_data)
113
114
114 return render('index_repo_group.html')
115 return render('index_repo_group.html')
115
116
116 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
117 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
117 query = Repository.query()\
118 query = Repository.query()\
118 .order_by(func.length(Repository.repo_name))\
119 .order_by(func.length(Repository.repo_name))\
119 .order_by(Repository.repo_name)
120 .order_by(Repository.repo_name)
120
121
121 if repo_type:
122 if repo_type:
122 query = query.filter(Repository.repo_type == repo_type)
123 query = query.filter(Repository.repo_type == repo_type)
123
124
124 if name_contains:
125 if name_contains:
125 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
126 query = query.filter(
127 query = query.filter(
127 Repository.repo_name.ilike(ilike_expression))
128 Repository.repo_name.ilike(ilike_expression))
128 query = query.limit(limit)
129 query = query.limit(limit)
129
130
130 all_repos = query.all()
131 all_repos = query.all()
131 repo_iter = self.scm_model.get_repos(all_repos)
132 repo_iter = self.scm_model.get_repos(all_repos)
132 return [
133 return [
133 {
134 {
134 'id': obj['name'],
135 'id': obj['name'],
135 'text': obj['name'],
136 'text': obj['name'],
136 'type': 'repo',
137 'type': 'repo',
137 'obj': obj['dbrepo']
138 'obj': obj['dbrepo'],
139 'url': url('summary_home', repo_name=obj['name'])
138 }
140 }
139 for obj in repo_iter]
141 for obj in repo_iter]
140
142
141 def _get_repo_group_list(self, name_contains=None, limit=20):
143 def _get_repo_group_list(self, name_contains=None, limit=20):
142 query = RepoGroup.query()\
144 query = RepoGroup.query()\
143 .order_by(func.length(RepoGroup.group_name))\
145 .order_by(func.length(RepoGroup.group_name))\
144 .order_by(RepoGroup.group_name)
146 .order_by(RepoGroup.group_name)
145
147
146 if name_contains:
148 if name_contains:
147 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
149 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
148 query = query.filter(
150 query = query.filter(
149 RepoGroup.group_name.ilike(ilike_expression))
151 RepoGroup.group_name.ilike(ilike_expression))
150 query = query.limit(limit)
152 query = query.limit(limit)
151
153
152 all_groups = query.all()
154 all_groups = query.all()
153 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
155 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
154 return [
156 return [
155 {
157 {
156 'id': obj.group_name,
158 'id': obj.group_name,
157 'text': obj.group_name,
159 'text': obj.group_name,
158 'type': 'group',
160 'type': 'group',
159 'obj': {}
161 'obj': {},
162 'url': url('repo_group_home', group_name=obj.group_name)
160 }
163 }
161 for obj in repo_groups_iter]
164 for obj in repo_groups_iter]
162
165
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
167 if not hash_starts_with or len(hash_starts_with) < 3:
168 return []
169
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
171
172 if len(commit_hashes) != 1:
173 return []
174
175 commit_hash_prefix = commit_hashes[0]
176
177 auth_user = AuthUser(
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
179 searcher = searcher_from_config(config)
180 result = searcher.search(
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
182
183 return [
184 {
185 'id': entry['commit_id'],
186 'text': entry['commit_id'],
187 'type': 'commit',
188 'obj': {'repo': entry['repository']},
189 'url': url('changeset_home',
190 repo_name=entry['repository'], revision=entry['commit_id'])
191 }
192 for entry in result['results']]
193
163 @LoginRequired()
194 @LoginRequired()
164 @XHRRequired()
195 @XHRRequired()
165 @jsonify
196 @jsonify
166 def repo_switcher_data(self):
197 def goto_switcher_data(self):
167 query = request.GET.get('query')
198 query = request.GET.get('query')
168 log.debug('generating switcher repo/groups list, query %s', query)
199 log.debug('generating goto switcher list, query %s', query)
169
200
170 res = []
201 res = []
171 repo_groups = self._get_repo_group_list(query)
202 repo_groups = self._get_repo_group_list(query)
172 if repo_groups:
203 if repo_groups:
173 res.append({
204 res.append({
174 'text': _('Groups'),
205 'text': _('Groups'),
175 'children': repo_groups
206 'children': repo_groups
176 })
207 })
177
208
178 repos = self._get_repo_list(query)
209 repos = self._get_repo_list(query)
179 if repos:
210 if repos:
180 res.append({
211 res.append({
181 'text': _('Repositories'),
212 'text': _('Repositories'),
182 'children': repos
213 'children': repos
183 })
214 })
184
215
216 commits = self._get_hash_commit_list(query)
217 if commits:
218 unique_repos = {}
219 for commit in commits:
220 unique_repos.setdefault(commit['obj']['repo'], []
221 ).append(commit)
222
223 for repo in unique_repos:
224 res.append({
225 'text': _('Commits in %(repo)s') % {'repo': repo},
226 'children': unique_repos[repo]
227 })
228
185 data = {
229 data = {
186 'more': False,
230 'more': False,
187 'results': res
231 'results': res
188 }
232 }
189 return data
233 return data
190
234
191 @LoginRequired()
235 @LoginRequired()
192 @XHRRequired()
236 @XHRRequired()
193 @jsonify
237 @jsonify
194 def repo_list_data(self):
238 def repo_list_data(self):
195 query = request.GET.get('query')
239 query = request.GET.get('query')
196 repo_type = request.GET.get('repo_type')
240 repo_type = request.GET.get('repo_type')
197 log.debug('generating repo list, query:%s', query)
241 log.debug('generating repo list, query:%s', query)
198
242
199 res = []
243 res = []
200 repos = self._get_repo_list(query, repo_type=repo_type)
244 repos = self._get_repo_list(query, repo_type=repo_type)
201 if repos:
245 if repos:
202 res.append({
246 res.append({
203 'text': _('Repositories'),
247 'text': _('Repositories'),
204 'children': repos
248 'children': repos
205 })
249 })
250
206 data = {
251 data = {
207 'more': False,
252 'more': False,
208 'results': res
253 'results': res
209 }
254 }
210 return data
255 return data
211
256
212 @LoginRequired()
257 @LoginRequired()
213 @XHRRequired()
258 @XHRRequired()
214 @jsonify
259 @jsonify
215 def user_autocomplete_data(self):
260 def user_autocomplete_data(self):
216 query = request.GET.get('query')
261 query = request.GET.get('query')
217
262
218 repo_model = RepoModel()
263 repo_model = RepoModel()
219 _users = repo_model.get_users(name_contains=query)
264 _users = repo_model.get_users(name_contains=query)
220
265
221 if request.GET.get('user_groups'):
266 if request.GET.get('user_groups'):
222 # extend with user groups
267 # extend with user groups
223 _user_groups = repo_model.get_user_groups(name_contains=query)
268 _user_groups = repo_model.get_user_groups(name_contains=query)
224 _users = _users + _user_groups
269 _users = _users + _user_groups
225
270
226 return {'suggestions': _users}
271 return {'suggestions': _users}
227
272
228 @LoginRequired()
273 @LoginRequired()
229 @XHRRequired()
274 @XHRRequired()
230 @jsonify
275 @jsonify
231 def user_group_autocomplete_data(self):
276 def user_group_autocomplete_data(self):
232 return {'suggestions': []}
277 return {'suggestions': []}
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/public/js/src/rhodecode/pyroutes.js to rhodecode/public/js/rhodecode/routes.js
NO CONTENT: file renamed from rhodecode/public/js/src/rhodecode/pyroutes.js to rhodecode/public/js/rhodecode/routes.js
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now