##// END OF EJS Templates
core: change from homebrew plugin system into pyramid machinery....
marcink -
r3240:b74ad312 default
parent child Browse files
Show More
@@ -1,147 +1,148 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from zope.interface import implementer
25 from zope.interface import implementer
26
26
27 from rhodecode.apps._base.interfaces import IAdminNavigationRegistry
27 from rhodecode.apps._base.interfaces import IAdminNavigationRegistry
28 from rhodecode.lib.utils2 import str2bool
28 from rhodecode.lib.utils2 import str2bool
29 from rhodecode.translation import _
29 from rhodecode.translation import _
30
30
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34 NavListEntry = collections.namedtuple(
34 NavListEntry = collections.namedtuple(
35 'NavListEntry', ['key', 'name', 'url', 'active_list'])
35 'NavListEntry', ['key', 'name', 'url', 'active_list'])
36
36
37
37
38 class NavEntry(object):
38 class NavEntry(object):
39 """
39 """
40 Represents an entry in the admin navigation.
40 Represents an entry in the admin navigation.
41
41
42 :param key: Unique identifier used to store reference in an OrderedDict.
42 :param key: Unique identifier used to store reference in an OrderedDict.
43 :param name: Display name, usually a translation string.
43 :param name: Display name, usually a translation string.
44 :param view_name: Name of the view, used generate the URL.
44 :param view_name: Name of the view, used generate the URL.
45 :param active_list: list of urls that we select active for this element
45 :param active_list: list of urls that we select active for this element
46 """
46 """
47
47
48 def __init__(self, key, name, view_name, active_list=None):
48 def __init__(self, key, name, view_name, active_list=None):
49 self.key = key
49 self.key = key
50 self.name = name
50 self.name = name
51 self.view_name = view_name
51 self.view_name = view_name
52 self._active_list = active_list or []
52 self._active_list = active_list or []
53
53
54 def generate_url(self, request):
54 def generate_url(self, request):
55 return request.route_path(self.view_name)
55 return request.route_path(self.view_name)
56
56
57 def get_localized_name(self, request):
57 def get_localized_name(self, request):
58 return request.translate(self.name)
58 return request.translate(self.name)
59
59
60 @property
60 @property
61 def active_list(self):
61 def active_list(self):
62 active_list = [self.key]
62 active_list = [self.key]
63 if self._active_list:
63 if self._active_list:
64 active_list = self._active_list
64 active_list = self._active_list
65 return active_list
65 return active_list
66
66
67
67
68 @implementer(IAdminNavigationRegistry)
68 @implementer(IAdminNavigationRegistry)
69 class NavigationRegistry(object):
69 class NavigationRegistry(object):
70
70
71 _base_entries = [
71 _base_entries = [
72 NavEntry('global', _('Global'),
72 NavEntry('global', _('Global'),
73 'admin_settings_global'),
73 'admin_settings_global'),
74 NavEntry('vcs', _('VCS'),
74 NavEntry('vcs', _('VCS'),
75 'admin_settings_vcs'),
75 'admin_settings_vcs'),
76 NavEntry('visual', _('Visual'),
76 NavEntry('visual', _('Visual'),
77 'admin_settings_visual'),
77 'admin_settings_visual'),
78 NavEntry('mapping', _('Remap and Rescan'),
78 NavEntry('mapping', _('Remap and Rescan'),
79 'admin_settings_mapping'),
79 'admin_settings_mapping'),
80 NavEntry('issuetracker', _('Issue Tracker'),
80 NavEntry('issuetracker', _('Issue Tracker'),
81 'admin_settings_issuetracker'),
81 'admin_settings_issuetracker'),
82 NavEntry('email', _('Email'),
82 NavEntry('email', _('Email'),
83 'admin_settings_email'),
83 'admin_settings_email'),
84 NavEntry('hooks', _('Hooks'),
84 NavEntry('hooks', _('Hooks'),
85 'admin_settings_hooks'),
85 'admin_settings_hooks'),
86 NavEntry('search', _('Full Text Search'),
86 NavEntry('search', _('Full Text Search'),
87 'admin_settings_search'),
87 'admin_settings_search'),
88 NavEntry('integrations', _('Integrations'),
88 NavEntry('integrations', _('Integrations'),
89 'global_integrations_home'),
89 'global_integrations_home'),
90 NavEntry('system', _('System Info'),
90 NavEntry('system', _('System Info'),
91 'admin_settings_system'),
91 'admin_settings_system'),
92 NavEntry('exceptions', _('Exceptions Tracker'),
92 NavEntry('exceptions', _('Exceptions Tracker'),
93 'admin_settings_exception_tracker',
93 'admin_settings_exception_tracker',
94 active_list=['exceptions', 'exceptions_browse']),
94 active_list=['exceptions', 'exceptions_browse']),
95 NavEntry('process_management', _('Processes'),
95 NavEntry('process_management', _('Processes'),
96 'admin_settings_process_management'),
96 'admin_settings_process_management'),
97 NavEntry('sessions', _('User Sessions'),
97 NavEntry('sessions', _('User Sessions'),
98 'admin_settings_sessions'),
98 'admin_settings_sessions'),
99 NavEntry('open_source', _('Open Source Licenses'),
99 NavEntry('open_source', _('Open Source Licenses'),
100 'admin_settings_open_source'),
100 'admin_settings_open_source'),
101 NavEntry('automation', _('Automation'),
101 NavEntry('automation', _('Automation'),
102 'admin_settings_automation')
102 'admin_settings_automation')
103 ]
103 ]
104
104
105 _labs_entry = NavEntry('labs', _('Labs'),
105 _labs_entry = NavEntry('labs', _('Labs'),
106 'admin_settings_labs')
106 'admin_settings_labs')
107
107
108 def __init__(self, labs_active=False):
108 def __init__(self, labs_active=False):
109 self._registered_entries = collections.OrderedDict()
109 self._registered_entries = collections.OrderedDict()
110 for item in self.__class__._base_entries:
110 for item in self.__class__._base_entries:
111 self._registered_entries[item.key] = item
111 self._registered_entries[item.key] = item
112
112
113 if labs_active:
113 if labs_active:
114 self.add_entry(self._labs_entry)
114 self.add_entry(self._labs_entry)
115
115
116 def add_entry(self, entry):
116 def add_entry(self, entry):
117 self._registered_entries[entry.key] = entry
117 self._registered_entries[entry.key] = entry
118
118
119 def get_navlist(self, request):
119 def get_navlist(self, request):
120 navlist = [NavListEntry(i.key, i.get_localized_name(request),
120 nav_list = [
121 i.generate_url(request), i.active_list)
121 NavListEntry(i.key, i.get_localized_name(request),
122 for i in self._registered_entries.values()]
122 i.generate_url(request), i.active_list)
123 return navlist
123 for i in self._registered_entries.values()]
124 return nav_list
124
125
125
126
126 def navigation_registry(request, registry=None):
127 def navigation_registry(request, registry=None):
127 """
128 """
128 Helper that returns the admin navigation registry.
129 Helper that returns the admin navigation registry.
129 """
130 """
130 pyramid_registry = registry or request.registry
131 pyramid_registry = registry or request.registry
131 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
132 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
132 return nav_registry
133 return nav_registry
133
134
134
135
135 def navigation_list(request):
136 def navigation_list(request):
136 """
137 """
137 Helper that returns the admin navigation as list of NavListEntry objects.
138 Helper that returns the admin navigation as list of NavListEntry objects.
138 """
139 """
139 return navigation_registry(request).get_navlist(request)
140 return navigation_registry(request).get_navlist(request)
140
141
141
142
142 def includeme(config):
143 def includeme(config):
143 # Create admin navigation registry and add it to the pyramid registry.
144 # Create admin navigation registry and add it to the pyramid registry.
144 settings = config.get_settings()
145 settings = config.get_settings()
145 labs_active = str2bool(settings.get('labs_settings_active', False))
146 labs_active = str2bool(settings.get('labs_settings_active', False))
146 navigation_registry = NavigationRegistry(labs_active=labs_active)
147 navigation_registry_instance = NavigationRegistry(labs_active=labs_active)
147 config.registry.registerUtility(navigation_registry) No newline at end of file
148 config.registry.registerUtility(navigation_registry_instance)
@@ -1,137 +1,111 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
22 import logging
21 import logging
23 import importlib
22 import importlib
24
23
25 from pkg_resources import iter_entry_points
26 from pyramid.authentication import SessionAuthenticationPolicy
24 from pyramid.authentication import SessionAuthenticationPolicy
27
25
28 from rhodecode.authentication.registry import AuthenticationPluginRegistry
26 from rhodecode.authentication.registry import AuthenticationPluginRegistry
29 from rhodecode.authentication.routes import root_factory
27 from rhodecode.authentication.routes import root_factory
30 from rhodecode.authentication.routes import AuthnRootResource
28 from rhodecode.authentication.routes import AuthnRootResource
31 from rhodecode.apps._base import ADMIN_PREFIX
29 from rhodecode.apps._base import ADMIN_PREFIX
32 from rhodecode.model.settings import SettingsModel
30 from rhodecode.model.settings import SettingsModel
33
31
34
35 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
36
33
37 # Plugin ID prefixes to distinct between normal and legacy plugins.
38 plugin_prefix = 'egg:'
39 legacy_plugin_prefix = 'py:'
34 legacy_plugin_prefix = 'py:'
40 plugin_default_auth_ttl = 30
35 plugin_default_auth_ttl = 30
41
36
42
37
43 # TODO: Currently this is only used to discover the authentication plugins.
44 # Later on this may be used in a generic way to look up and include all kinds
45 # of supported enterprise plugins. Therefore this has to be moved and
46 # refactored to a real 'plugin look up' machinery.
47 # TODO: When refactoring this think about splitting it up into distinct
48 # discover, load and include phases.
49 def _discover_plugins(config, entry_point='enterprise.plugins1'):
50 log.debug('authentication: running plugin discovery for entrypoint %s',
51 entry_point)
52
53 for ep in iter_entry_points(entry_point):
54 plugin_id = '{}{}#{}'.format(
55 plugin_prefix, ep.dist.project_name, ep.name)
56 log.debug('Plugin discovered: "%s"', plugin_id)
57 try:
58 module = ep.load()
59 plugin = module(plugin_id=plugin_id)
60 config.include(plugin.includeme)
61 except Exception as e:
62 log.exception(
63 'Exception while loading authentication plugin '
64 '"{}": {}'.format(plugin_id, e.message))
65
66
67 def _import_legacy_plugin(plugin_id):
38 def _import_legacy_plugin(plugin_id):
68 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
39 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
69 module = importlib.import_module(module_name)
40 module = importlib.import_module(module_name)
70 return module.plugin_factory(plugin_id=plugin_id)
41 return module.plugin_factory(plugin_id=plugin_id)
71
42
72
43
73 def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
44 def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
74 """
45 """
75 Function that imports the legacy plugins stored in the 'auth_plugins'
46 Function that imports the legacy plugins stored in the 'auth_plugins'
76 setting in database which are using the specified prefix. Normally 'py:' is
47 setting in database which are using the specified prefix. Normally 'py:' is
77 used for the legacy plugins.
48 used for the legacy plugins.
78 """
49 """
79 log.debug('authentication: running legacy plugin discovery for prefix %s',
50 log.debug('authentication: running legacy plugin discovery for prefix %s',
80 legacy_plugin_prefix)
51 legacy_plugin_prefix)
81 try:
52 try:
82 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
53 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
83 enabled_plugins = auth_plugins.app_settings_value
54 enabled_plugins = auth_plugins.app_settings_value
84 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
55 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
85 except Exception:
56 except Exception:
86 legacy_plugins = []
57 legacy_plugins = []
87
58
88 for plugin_id in legacy_plugins:
59 for plugin_id in legacy_plugins:
89 log.debug('Legacy plugin discovered: "%s"', plugin_id)
60 log.debug('Legacy plugin discovered: "%s"', plugin_id)
90 try:
61 try:
91 plugin = _import_legacy_plugin(plugin_id)
62 plugin = _import_legacy_plugin(plugin_id)
92 config.include(plugin.includeme)
63 config.include(plugin.includeme)
93 except Exception as e:
64 except Exception as e:
94 log.exception(
65 log.exception(
95 'Exception while loading legacy authentication plugin '
66 'Exception while loading legacy authentication plugin '
96 '"{}": {}'.format(plugin_id, e.message))
67 '"{}": {}'.format(plugin_id, e.message))
97
68
98
69
99 def includeme(config):
70 def includeme(config):
100 # Set authentication policy.
71 # Set authentication policy.
101 authn_policy = SessionAuthenticationPolicy()
72 authn_policy = SessionAuthenticationPolicy()
102 config.set_authentication_policy(authn_policy)
73 config.set_authentication_policy(authn_policy)
103
74
104 # Create authentication plugin registry and add it to the pyramid registry.
75 # Create authentication plugin registry and add it to the pyramid registry.
105 authn_registry = AuthenticationPluginRegistry(config.get_settings())
76 authn_registry = AuthenticationPluginRegistry(config.get_settings())
106 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
77 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
107 config.registry.registerUtility(authn_registry)
78 config.registry.registerUtility(authn_registry)
108
79
109 # Create authentication traversal root resource.
80 # Create authentication traversal root resource.
110 authn_root_resource = root_factory()
81 authn_root_resource = root_factory()
111 config.add_directive('add_authn_resource',
82 config.add_directive('add_authn_resource',
112 authn_root_resource.add_authn_resource)
83 authn_root_resource.add_authn_resource)
113
84
114 # Add the authentication traversal route.
85 # Add the authentication traversal route.
115 config.add_route('auth_home',
86 config.add_route('auth_home',
116 ADMIN_PREFIX + '/auth*traverse',
87 ADMIN_PREFIX + '/auth*traverse',
117 factory=root_factory)
88 factory=root_factory)
118 # Add the authentication settings root views.
89 # Add the authentication settings root views.
119 config.add_view('rhodecode.authentication.views.AuthSettingsView',
90 config.add_view('rhodecode.authentication.views.AuthSettingsView',
120 attr='index',
91 attr='index',
121 request_method='GET',
92 request_method='GET',
122 route_name='auth_home',
93 route_name='auth_home',
123 context=AuthnRootResource)
94 context=AuthnRootResource)
124 config.add_view('rhodecode.authentication.views.AuthSettingsView',
95 config.add_view('rhodecode.authentication.views.AuthSettingsView',
125 attr='auth_settings',
96 attr='auth_settings',
126 request_method='POST',
97 request_method='POST',
127 route_name='auth_home',
98 route_name='auth_home',
128 context=AuthnRootResource)
99 context=AuthnRootResource)
129
100
130 for key in ['RC_CMD_SETUP_RC', 'RC_CMD_UPGRADE_DB', 'RC_CMD_SSH_WRAPPER']:
101 # load CE authentication plugins
131 if os.environ.get(key):
102 config.include('rhodecode.authentication.plugins.auth_crowd')
132 # skip this heavy step below on certain CLI commands
103 config.include('rhodecode.authentication.plugins.auth_headers')
133 return
104 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
105 config.include('rhodecode.authentication.plugins.auth_ldap')
106 config.include('rhodecode.authentication.plugins.auth_pam')
107 config.include('rhodecode.authentication.plugins.auth_rhodecode')
108 config.include('rhodecode.authentication.plugins.auth_token')
134
109
135 # Auto discover authentication plugins and include their configuration.
110 # Auto discover authentication plugins and include their configuration.
136 _discover_plugins(config)
137 _discover_legacy_plugins(config)
111 _discover_legacy_plugins(config)
@@ -1,289 +1,294 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 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 rhodecode.translation import _
31 from rhodecode.translation import _
32 from rhodecode.authentication.base import (
32 from rhodecode.authentication.base import (
33 RhodeCodeExternalAuthPlugin, hybrid_property)
33 RhodeCodeExternalAuthPlugin, hybrid_property)
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.colander_utils import strip_whitespace
37 from rhodecode.lib.ext_json import json, formatted_json
37 from rhodecode.lib.ext_json import json, formatted_json
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 def plugin_factory(plugin_id, *args, **kwds):
43 def plugin_factory(plugin_id, *args, **kwds):
44 """
44 """
45 Factory function that is called during plugin discovery.
45 Factory function that is called during plugin discovery.
46 It returns the plugin instance.
46 It returns the plugin instance.
47 """
47 """
48 plugin = RhodeCodeAuthPlugin(plugin_id)
48 plugin = RhodeCodeAuthPlugin(plugin_id)
49 return plugin
49 return plugin
50
50
51
51
52 class CrowdAuthnResource(AuthnPluginResourceBase):
52 class CrowdAuthnResource(AuthnPluginResourceBase):
53 pass
53 pass
54
54
55
55
56 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
56 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
57 host = colander.SchemaNode(
57 host = colander.SchemaNode(
58 colander.String(),
58 colander.String(),
59 default='127.0.0.1',
59 default='127.0.0.1',
60 description=_('The FQDN or IP of the Atlassian CROWD Server'),
60 description=_('The FQDN or IP of the Atlassian CROWD Server'),
61 preparer=strip_whitespace,
61 preparer=strip_whitespace,
62 title=_('Host'),
62 title=_('Host'),
63 widget='string')
63 widget='string')
64 port = colander.SchemaNode(
64 port = colander.SchemaNode(
65 colander.Int(),
65 colander.Int(),
66 default=8095,
66 default=8095,
67 description=_('The Port in use by the Atlassian CROWD Server'),
67 description=_('The Port in use by the Atlassian CROWD Server'),
68 preparer=strip_whitespace,
68 preparer=strip_whitespace,
69 title=_('Port'),
69 title=_('Port'),
70 validator=colander.Range(min=0, max=65536),
70 validator=colander.Range(min=0, max=65536),
71 widget='int')
71 widget='int')
72 app_name = colander.SchemaNode(
72 app_name = colander.SchemaNode(
73 colander.String(),
73 colander.String(),
74 default='',
74 default='',
75 description=_('The Application Name to authenticate to CROWD'),
75 description=_('The Application Name to authenticate to CROWD'),
76 preparer=strip_whitespace,
76 preparer=strip_whitespace,
77 title=_('Application Name'),
77 title=_('Application Name'),
78 widget='string')
78 widget='string')
79 app_password = colander.SchemaNode(
79 app_password = colander.SchemaNode(
80 colander.String(),
80 colander.String(),
81 default='',
81 default='',
82 description=_('The password to authenticate to CROWD'),
82 description=_('The password to authenticate to CROWD'),
83 preparer=strip_whitespace,
83 preparer=strip_whitespace,
84 title=_('Application Password'),
84 title=_('Application Password'),
85 widget='password')
85 widget='password')
86 admin_groups = colander.SchemaNode(
86 admin_groups = colander.SchemaNode(
87 colander.String(),
87 colander.String(),
88 default='',
88 default='',
89 description=_('A comma separated list of group names that identify '
89 description=_('A comma separated list of group names that identify '
90 'users as RhodeCode Administrators'),
90 'users as RhodeCode Administrators'),
91 missing='',
91 missing='',
92 preparer=strip_whitespace,
92 preparer=strip_whitespace,
93 title=_('Admin Groups'),
93 title=_('Admin Groups'),
94 widget='string')
94 widget='string')
95
95
96
96
97 class CrowdServer(object):
97 class CrowdServer(object):
98 def __init__(self, *args, **kwargs):
98 def __init__(self, *args, **kwargs):
99 """
99 """
100 Create a new CrowdServer object that points to IP/Address 'host',
100 Create a new CrowdServer object that points to IP/Address 'host',
101 on the given port, and using the given method (https/http). user and
101 on the given port, and using the given method (https/http). user and
102 passwd can be set here or with set_credentials. If unspecified,
102 passwd can be set here or with set_credentials. If unspecified,
103 "version" defaults to "latest".
103 "version" defaults to "latest".
104
104
105 example::
105 example::
106
106
107 cserver = CrowdServer(host="127.0.0.1",
107 cserver = CrowdServer(host="127.0.0.1",
108 port="8095",
108 port="8095",
109 user="some_app",
109 user="some_app",
110 passwd="some_passwd",
110 passwd="some_passwd",
111 version="1")
111 version="1")
112 """
112 """
113 if not "port" in kwargs:
113 if not "port" in kwargs:
114 kwargs["port"] = "8095"
114 kwargs["port"] = "8095"
115 self._logger = kwargs.get("logger", logging.getLogger(__name__))
115 self._logger = kwargs.get("logger", logging.getLogger(__name__))
116 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
116 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
117 kwargs.get("host", "127.0.0.1"),
117 kwargs.get("host", "127.0.0.1"),
118 kwargs.get("port", "8095"))
118 kwargs.get("port", "8095"))
119 self.set_credentials(kwargs.get("user", ""),
119 self.set_credentials(kwargs.get("user", ""),
120 kwargs.get("passwd", ""))
120 kwargs.get("passwd", ""))
121 self._version = kwargs.get("version", "latest")
121 self._version = kwargs.get("version", "latest")
122 self._url_list = None
122 self._url_list = None
123 self._appname = "crowd"
123 self._appname = "crowd"
124
124
125 def set_credentials(self, user, passwd):
125 def set_credentials(self, user, passwd):
126 self.user = user
126 self.user = user
127 self.passwd = passwd
127 self.passwd = passwd
128 self._make_opener()
128 self._make_opener()
129
129
130 def _make_opener(self):
130 def _make_opener(self):
131 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
131 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
132 mgr.add_password(None, self._uri, self.user, self.passwd)
132 mgr.add_password(None, self._uri, self.user, self.passwd)
133 handler = urllib2.HTTPBasicAuthHandler(mgr)
133 handler = urllib2.HTTPBasicAuthHandler(mgr)
134 self.opener = urllib2.build_opener(handler)
134 self.opener = urllib2.build_opener(handler)
135
135
136 def _request(self, url, body=None, headers=None,
136 def _request(self, url, body=None, headers=None,
137 method=None, noformat=False,
137 method=None, noformat=False,
138 empty_response_ok=False):
138 empty_response_ok=False):
139 _headers = {"Content-type": "application/json",
139 _headers = {"Content-type": "application/json",
140 "Accept": "application/json"}
140 "Accept": "application/json"}
141 if self.user and self.passwd:
141 if self.user and self.passwd:
142 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
142 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
143 _headers["Authorization"] = "Basic %s" % authstring
143 _headers["Authorization"] = "Basic %s" % authstring
144 if headers:
144 if headers:
145 _headers.update(headers)
145 _headers.update(headers)
146 log.debug("Sent crowd: \n%s"
146 log.debug("Sent crowd: \n%s"
147 % (formatted_json({"url": url, "body": body,
147 % (formatted_json({"url": url, "body": body,
148 "headers": _headers})))
148 "headers": _headers})))
149 request = urllib2.Request(url, body, _headers)
149 request = urllib2.Request(url, body, _headers)
150 if method:
150 if method:
151 request.get_method = lambda: method
151 request.get_method = lambda: method
152
152
153 global msg
153 global msg
154 msg = ""
154 msg = ""
155 try:
155 try:
156 rdoc = self.opener.open(request)
156 rdoc = self.opener.open(request)
157 msg = "".join(rdoc.readlines())
157 msg = "".join(rdoc.readlines())
158 if not msg and empty_response_ok:
158 if not msg and empty_response_ok:
159 rval = {}
159 rval = {}
160 rval["status"] = True
160 rval["status"] = True
161 rval["error"] = "Response body was empty"
161 rval["error"] = "Response body was empty"
162 elif not noformat:
162 elif not noformat:
163 rval = json.loads(msg)
163 rval = json.loads(msg)
164 rval["status"] = True
164 rval["status"] = True
165 else:
165 else:
166 rval = "".join(rdoc.readlines())
166 rval = "".join(rdoc.readlines())
167 except Exception as e:
167 except Exception as e:
168 if not noformat:
168 if not noformat:
169 rval = {"status": False,
169 rval = {"status": False,
170 "body": body,
170 "body": body,
171 "error": str(e) + "\n" + msg}
171 "error": str(e) + "\n" + msg}
172 else:
172 else:
173 rval = None
173 rval = None
174 return rval
174 return rval
175
175
176 def user_auth(self, username, password):
176 def user_auth(self, username, password):
177 """Authenticate a user against crowd. Returns brief information about
177 """Authenticate a user against crowd. Returns brief information about
178 the user."""
178 the user."""
179 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
179 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
180 % (self._uri, self._version, username))
180 % (self._uri, self._version, username))
181 body = json.dumps({"value": password})
181 body = json.dumps({"value": password})
182 return self._request(url, body)
182 return self._request(url, body)
183
183
184 def user_groups(self, username):
184 def user_groups(self, username):
185 """Retrieve a list of groups to which this user belongs."""
185 """Retrieve a list of groups to which this user belongs."""
186 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
186 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
187 % (self._uri, self._version, username))
187 % (self._uri, self._version, username))
188 return self._request(url)
188 return self._request(url)
189
189
190
190
191 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
191 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
192 _settings_unsafe_keys = ['app_password']
192 _settings_unsafe_keys = ['app_password']
193
193
194 def includeme(self, config):
194 def includeme(self, config):
195 config.add_authn_plugin(self)
195 config.add_authn_plugin(self)
196 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
196 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
197 config.add_view(
197 config.add_view(
198 'rhodecode.authentication.views.AuthnPluginViewBase',
198 'rhodecode.authentication.views.AuthnPluginViewBase',
199 attr='settings_get',
199 attr='settings_get',
200 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
200 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
201 request_method='GET',
201 request_method='GET',
202 route_name='auth_home',
202 route_name='auth_home',
203 context=CrowdAuthnResource)
203 context=CrowdAuthnResource)
204 config.add_view(
204 config.add_view(
205 'rhodecode.authentication.views.AuthnPluginViewBase',
205 'rhodecode.authentication.views.AuthnPluginViewBase',
206 attr='settings_post',
206 attr='settings_post',
207 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
207 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
208 request_method='POST',
208 request_method='POST',
209 route_name='auth_home',
209 route_name='auth_home',
210 context=CrowdAuthnResource)
210 context=CrowdAuthnResource)
211
211
212 def get_settings_schema(self):
212 def get_settings_schema(self):
213 return CrowdSettingsSchema()
213 return CrowdSettingsSchema()
214
214
215 def get_display_name(self):
215 def get_display_name(self):
216 return _('CROWD')
216 return _('CROWD')
217
217
218 @classmethod
218 @classmethod
219 def docs(cls):
219 def docs(cls):
220 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-crowd.html"
220 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-crowd.html"
221
221
222 @hybrid_property
222 @hybrid_property
223 def name(self):
223 def name(self):
224 return "crowd"
224 return "crowd"
225
225
226 def use_fake_password(self):
226 def use_fake_password(self):
227 return True
227 return True
228
228
229 def user_activation_state(self):
229 def user_activation_state(self):
230 def_user_perms = User.get_default_user().AuthUser().permissions['global']
230 def_user_perms = User.get_default_user().AuthUser().permissions['global']
231 return 'hg.extern_activate.auto' in def_user_perms
231 return 'hg.extern_activate.auto' in def_user_perms
232
232
233 def auth(self, userobj, username, password, settings, **kwargs):
233 def auth(self, userobj, username, password, settings, **kwargs):
234 """
234 """
235 Given a user object (which may be null), username, a plaintext password,
235 Given a user object (which may be null), username, a plaintext password,
236 and a settings object (containing all the keys needed as listed in settings()),
236 and a settings object (containing all the keys needed as listed in settings()),
237 authenticate this user's login attempt.
237 authenticate this user's login attempt.
238
238
239 Return None on failure. On success, return a dictionary of the form:
239 Return None on failure. On success, return a dictionary of the form:
240
240
241 see: RhodeCodeAuthPluginBase.auth_func_attrs
241 see: RhodeCodeAuthPluginBase.auth_func_attrs
242 This is later validated for correctness
242 This is later validated for correctness
243 """
243 """
244 if not username or not password:
244 if not username or not password:
245 log.debug('Empty username or password skipping...')
245 log.debug('Empty username or password skipping...')
246 return None
246 return None
247
247
248 log.debug("Crowd settings: \n%s", formatted_json(settings))
248 log.debug("Crowd settings: \n%s", formatted_json(settings))
249 server = CrowdServer(**settings)
249 server = CrowdServer(**settings)
250 server.set_credentials(settings["app_name"], settings["app_password"])
250 server.set_credentials(settings["app_name"], settings["app_password"])
251 crowd_user = server.user_auth(username, password)
251 crowd_user = server.user_auth(username, password)
252 log.debug("Crowd returned: \n%s", formatted_json(crowd_user))
252 log.debug("Crowd returned: \n%s", formatted_json(crowd_user))
253 if not crowd_user["status"]:
253 if not crowd_user["status"]:
254 return None
254 return None
255
255
256 res = server.user_groups(crowd_user["name"])
256 res = server.user_groups(crowd_user["name"])
257 log.debug("Crowd groups: \n%s", formatted_json(res))
257 log.debug("Crowd groups: \n%s", formatted_json(res))
258 crowd_user["groups"] = [x["name"] for x in res["groups"]]
258 crowd_user["groups"] = [x["name"] for x in res["groups"]]
259
259
260 # old attrs fetched from RhodeCode database
260 # old attrs fetched from RhodeCode database
261 admin = getattr(userobj, 'admin', False)
261 admin = getattr(userobj, 'admin', False)
262 active = getattr(userobj, 'active', True)
262 active = getattr(userobj, 'active', True)
263 email = getattr(userobj, 'email', '')
263 email = getattr(userobj, 'email', '')
264 username = getattr(userobj, 'username', username)
264 username = getattr(userobj, 'username', username)
265 firstname = getattr(userobj, 'firstname', '')
265 firstname = getattr(userobj, 'firstname', '')
266 lastname = getattr(userobj, 'lastname', '')
266 lastname = getattr(userobj, 'lastname', '')
267 extern_type = getattr(userobj, 'extern_type', '')
267 extern_type = getattr(userobj, 'extern_type', '')
268
268
269 user_attrs = {
269 user_attrs = {
270 'username': username,
270 'username': username,
271 'firstname': crowd_user["first-name"] or firstname,
271 'firstname': crowd_user["first-name"] or firstname,
272 'lastname': crowd_user["last-name"] or lastname,
272 'lastname': crowd_user["last-name"] or lastname,
273 'groups': crowd_user["groups"],
273 'groups': crowd_user["groups"],
274 'user_group_sync': True,
274 'user_group_sync': True,
275 'email': crowd_user["email"] or email,
275 'email': crowd_user["email"] or email,
276 'admin': admin,
276 'admin': admin,
277 'active': active,
277 'active': active,
278 'active_from_extern': crowd_user.get('active'),
278 'active_from_extern': crowd_user.get('active'),
279 'extern_name': crowd_user["name"],
279 'extern_name': crowd_user["name"],
280 'extern_type': extern_type,
280 'extern_type': extern_type,
281 }
281 }
282
282
283 # set an admin if we're in admin_groups of crowd
283 # set an admin if we're in admin_groups of crowd
284 for group in settings["admin_groups"]:
284 for group in settings["admin_groups"]:
285 if group in user_attrs["groups"]:
285 if group in user_attrs["groups"]:
286 user_attrs["admin"] = True
286 user_attrs["admin"] = True
287 log.debug("Final crowd user object: \n%s", formatted_json(user_attrs))
287 log.debug("Final crowd user object: \n%s", formatted_json(user_attrs))
288 log.info('user `%s` authenticated correctly', user_attrs['username'])
288 log.info('user `%s` authenticated correctly', user_attrs['username'])
289 return user_attrs
289 return user_attrs
290
291
292 def includeme(config):
293 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format('crowd')
294 plugin_factory(plugin_id).includeme(config)
@@ -1,225 +1,230 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 import logging
22 import logging
23
23
24 from rhodecode.translation import _
24 from rhodecode.translation import _
25 from rhodecode.authentication.base import (
25 from rhodecode.authentication.base import (
26 RhodeCodeExternalAuthPlugin, hybrid_property)
26 RhodeCodeExternalAuthPlugin, hybrid_property)
27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
28 from rhodecode.authentication.routes import AuthnPluginResourceBase
28 from rhodecode.authentication.routes import AuthnPluginResourceBase
29 from rhodecode.lib.colander_utils import strip_whitespace
29 from rhodecode.lib.colander_utils import strip_whitespace
30 from rhodecode.lib.utils2 import str2bool, safe_unicode
30 from rhodecode.lib.utils2 import str2bool, safe_unicode
31 from rhodecode.model.db import User
31 from rhodecode.model.db import User
32
32
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 def plugin_factory(plugin_id, *args, **kwds):
37 def plugin_factory(plugin_id, *args, **kwds):
38 """
38 """
39 Factory function that is called during plugin discovery.
39 Factory function that is called during plugin discovery.
40 It returns the plugin instance.
40 It returns the plugin instance.
41 """
41 """
42 plugin = RhodeCodeAuthPlugin(plugin_id)
42 plugin = RhodeCodeAuthPlugin(plugin_id)
43 return plugin
43 return plugin
44
44
45
45
46 class HeadersAuthnResource(AuthnPluginResourceBase):
46 class HeadersAuthnResource(AuthnPluginResourceBase):
47 pass
47 pass
48
48
49
49
50 class HeadersSettingsSchema(AuthnPluginSettingsSchemaBase):
50 class HeadersSettingsSchema(AuthnPluginSettingsSchemaBase):
51 header = colander.SchemaNode(
51 header = colander.SchemaNode(
52 colander.String(),
52 colander.String(),
53 default='REMOTE_USER',
53 default='REMOTE_USER',
54 description=_('Header to extract the user from'),
54 description=_('Header to extract the user from'),
55 preparer=strip_whitespace,
55 preparer=strip_whitespace,
56 title=_('Header'),
56 title=_('Header'),
57 widget='string')
57 widget='string')
58 fallback_header = colander.SchemaNode(
58 fallback_header = colander.SchemaNode(
59 colander.String(),
59 colander.String(),
60 default='HTTP_X_FORWARDED_USER',
60 default='HTTP_X_FORWARDED_USER',
61 description=_('Header to extract the user from when main one fails'),
61 description=_('Header to extract the user from when main one fails'),
62 preparer=strip_whitespace,
62 preparer=strip_whitespace,
63 title=_('Fallback header'),
63 title=_('Fallback header'),
64 widget='string')
64 widget='string')
65 clean_username = colander.SchemaNode(
65 clean_username = colander.SchemaNode(
66 colander.Boolean(),
66 colander.Boolean(),
67 default=True,
67 default=True,
68 description=_('Perform cleaning of user, if passed user has @ in '
68 description=_('Perform cleaning of user, if passed user has @ in '
69 'username then first part before @ is taken. '
69 'username then first part before @ is taken. '
70 'If there\'s \\ in the username only the part after '
70 'If there\'s \\ in the username only the part after '
71 ' \\ is taken'),
71 ' \\ is taken'),
72 missing=False,
72 missing=False,
73 title=_('Clean username'),
73 title=_('Clean username'),
74 widget='bool')
74 widget='bool')
75
75
76
76
77 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
77 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
78
78
79 def includeme(self, config):
79 def includeme(self, config):
80 config.add_authn_plugin(self)
80 config.add_authn_plugin(self)
81 config.add_authn_resource(self.get_id(), HeadersAuthnResource(self))
81 config.add_authn_resource(self.get_id(), HeadersAuthnResource(self))
82 config.add_view(
82 config.add_view(
83 'rhodecode.authentication.views.AuthnPluginViewBase',
83 'rhodecode.authentication.views.AuthnPluginViewBase',
84 attr='settings_get',
84 attr='settings_get',
85 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
85 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
86 request_method='GET',
86 request_method='GET',
87 route_name='auth_home',
87 route_name='auth_home',
88 context=HeadersAuthnResource)
88 context=HeadersAuthnResource)
89 config.add_view(
89 config.add_view(
90 'rhodecode.authentication.views.AuthnPluginViewBase',
90 'rhodecode.authentication.views.AuthnPluginViewBase',
91 attr='settings_post',
91 attr='settings_post',
92 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
92 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
93 request_method='POST',
93 request_method='POST',
94 route_name='auth_home',
94 route_name='auth_home',
95 context=HeadersAuthnResource)
95 context=HeadersAuthnResource)
96
96
97 def get_display_name(self):
97 def get_display_name(self):
98 return _('Headers')
98 return _('Headers')
99
99
100 def get_settings_schema(self):
100 def get_settings_schema(self):
101 return HeadersSettingsSchema()
101 return HeadersSettingsSchema()
102
102
103 @hybrid_property
103 @hybrid_property
104 def name(self):
104 def name(self):
105 return 'headers'
105 return 'headers'
106
106
107 @property
107 @property
108 def is_headers_auth(self):
108 def is_headers_auth(self):
109 return True
109 return True
110
110
111 def use_fake_password(self):
111 def use_fake_password(self):
112 return True
112 return True
113
113
114 def user_activation_state(self):
114 def user_activation_state(self):
115 def_user_perms = User.get_default_user().AuthUser().permissions['global']
115 def_user_perms = User.get_default_user().AuthUser().permissions['global']
116 return 'hg.extern_activate.auto' in def_user_perms
116 return 'hg.extern_activate.auto' in def_user_perms
117
117
118 def _clean_username(self, username):
118 def _clean_username(self, username):
119 # Removing realm and domain from username
119 # Removing realm and domain from username
120 username = username.split('@')[0]
120 username = username.split('@')[0]
121 username = username.rsplit('\\')[-1]
121 username = username.rsplit('\\')[-1]
122 return username
122 return username
123
123
124 def _get_username(self, environ, settings):
124 def _get_username(self, environ, settings):
125 username = None
125 username = None
126 environ = environ or {}
126 environ = environ or {}
127 if not environ:
127 if not environ:
128 log.debug('got empty environ: %s', environ)
128 log.debug('got empty environ: %s', environ)
129
129
130 settings = settings or {}
130 settings = settings or {}
131 if settings.get('header'):
131 if settings.get('header'):
132 header = settings.get('header')
132 header = settings.get('header')
133 username = environ.get(header)
133 username = environ.get(header)
134 log.debug('extracted %s:%s', header, username)
134 log.debug('extracted %s:%s', header, username)
135
135
136 # fallback mode
136 # fallback mode
137 if not username and settings.get('fallback_header'):
137 if not username and settings.get('fallback_header'):
138 header = settings.get('fallback_header')
138 header = settings.get('fallback_header')
139 username = environ.get(header)
139 username = environ.get(header)
140 log.debug('extracted %s:%s', header, username)
140 log.debug('extracted %s:%s', header, username)
141
141
142 if username and str2bool(settings.get('clean_username')):
142 if username and str2bool(settings.get('clean_username')):
143 log.debug('Received username `%s` from headers', username)
143 log.debug('Received username `%s` from headers', username)
144 username = self._clean_username(username)
144 username = self._clean_username(username)
145 log.debug('New cleanup user is:%s', username)
145 log.debug('New cleanup user is:%s', username)
146 return username
146 return username
147
147
148 def get_user(self, username=None, **kwargs):
148 def get_user(self, username=None, **kwargs):
149 """
149 """
150 Helper method for user fetching in plugins, by default it's using
150 Helper method for user fetching in plugins, by default it's using
151 simple fetch by username, but this method can be custimized in plugins
151 simple fetch by username, but this method can be custimized in plugins
152 eg. headers auth plugin to fetch user by environ params
152 eg. headers auth plugin to fetch user by environ params
153 :param username: username if given to fetch
153 :param username: username if given to fetch
154 :param kwargs: extra arguments needed for user fetching.
154 :param kwargs: extra arguments needed for user fetching.
155 """
155 """
156 environ = kwargs.get('environ') or {}
156 environ = kwargs.get('environ') or {}
157 settings = kwargs.get('settings') or {}
157 settings = kwargs.get('settings') or {}
158 username = self._get_username(environ, settings)
158 username = self._get_username(environ, settings)
159 # we got the username, so use default method now
159 # we got the username, so use default method now
160 return super(RhodeCodeAuthPlugin, self).get_user(username)
160 return super(RhodeCodeAuthPlugin, self).get_user(username)
161
161
162 def auth(self, userobj, username, password, settings, **kwargs):
162 def auth(self, userobj, username, password, settings, **kwargs):
163 """
163 """
164 Get's the headers_auth username (or email). It tries to get username
164 Get's the headers_auth username (or email). It tries to get username
165 from REMOTE_USER if this plugin is enabled, if that fails
165 from REMOTE_USER if this plugin is enabled, if that fails
166 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
166 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
167 is set. clean_username extracts the username from this data if it's
167 is set. clean_username extracts the username from this data if it's
168 having @ in it.
168 having @ in it.
169 Return None on failure. On success, return a dictionary of the form:
169 Return None on failure. On success, return a dictionary of the form:
170
170
171 see: RhodeCodeAuthPluginBase.auth_func_attrs
171 see: RhodeCodeAuthPluginBase.auth_func_attrs
172
172
173 :param userobj:
173 :param userobj:
174 :param username:
174 :param username:
175 :param password:
175 :param password:
176 :param settings:
176 :param settings:
177 :param kwargs:
177 :param kwargs:
178 """
178 """
179 environ = kwargs.get('environ')
179 environ = kwargs.get('environ')
180 if not environ:
180 if not environ:
181 log.debug('Empty environ data skipping...')
181 log.debug('Empty environ data skipping...')
182 return None
182 return None
183
183
184 if not userobj:
184 if not userobj:
185 userobj = self.get_user('', environ=environ, settings=settings)
185 userobj = self.get_user('', environ=environ, settings=settings)
186
186
187 # we don't care passed username/password for headers auth plugins.
187 # we don't care passed username/password for headers auth plugins.
188 # only way to log in is using environ
188 # only way to log in is using environ
189 username = None
189 username = None
190 if userobj:
190 if userobj:
191 username = getattr(userobj, 'username')
191 username = getattr(userobj, 'username')
192
192
193 if not username:
193 if not username:
194 # we don't have any objects in DB user doesn't exist extract
194 # we don't have any objects in DB user doesn't exist extract
195 # username from environ based on the settings
195 # username from environ based on the settings
196 username = self._get_username(environ, settings)
196 username = self._get_username(environ, settings)
197
197
198 # if cannot fetch username, it's a no-go for this plugin to proceed
198 # if cannot fetch username, it's a no-go for this plugin to proceed
199 if not username:
199 if not username:
200 return None
200 return None
201
201
202 # old attrs fetched from RhodeCode database
202 # old attrs fetched from RhodeCode database
203 admin = getattr(userobj, 'admin', False)
203 admin = getattr(userobj, 'admin', False)
204 active = getattr(userobj, 'active', True)
204 active = getattr(userobj, 'active', True)
205 email = getattr(userobj, 'email', '')
205 email = getattr(userobj, 'email', '')
206 firstname = getattr(userobj, 'firstname', '')
206 firstname = getattr(userobj, 'firstname', '')
207 lastname = getattr(userobj, 'lastname', '')
207 lastname = getattr(userobj, 'lastname', '')
208 extern_type = getattr(userobj, 'extern_type', '')
208 extern_type = getattr(userobj, 'extern_type', '')
209
209
210 user_attrs = {
210 user_attrs = {
211 'username': username,
211 'username': username,
212 'firstname': safe_unicode(firstname or username),
212 'firstname': safe_unicode(firstname or username),
213 'lastname': safe_unicode(lastname or ''),
213 'lastname': safe_unicode(lastname or ''),
214 'groups': [],
214 'groups': [],
215 'user_group_sync': False,
215 'user_group_sync': False,
216 'email': email or '',
216 'email': email or '',
217 'admin': admin or False,
217 'admin': admin or False,
218 'active': active,
218 'active': active,
219 'active_from_extern': True,
219 'active_from_extern': True,
220 'extern_name': username,
220 'extern_name': username,
221 'extern_type': extern_type,
221 'extern_type': extern_type,
222 }
222 }
223
223
224 log.info('user `%s` authenticated correctly', user_attrs['username'])
224 log.info('user `%s` authenticated correctly', user_attrs['username'])
225 return user_attrs
225 return user_attrs
226
227
228 def includeme(config):
229 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format('headers')
230 plugin_factory(plugin_id).includeme(config)
@@ -1,167 +1,172 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 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 rhodecode.translation import _
33 from rhodecode.translation import _
34 from rhodecode.authentication.base import (
34 from rhodecode.authentication.base import (
35 RhodeCodeExternalAuthPlugin, hybrid_property)
35 RhodeCodeExternalAuthPlugin, hybrid_property)
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 from rhodecode.lib.colander_utils import strip_whitespace
39 from rhodecode.lib.utils2 import safe_unicode
39 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.model.db import User
40 from rhodecode.model.db import User
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 def plugin_factory(plugin_id, *args, **kwds):
45 def plugin_factory(plugin_id, *args, **kwds):
46 """
46 """
47 Factory function that is called during plugin discovery.
47 Factory function that is called during plugin discovery.
48 It returns the plugin instance.
48 It returns the plugin instance.
49 """
49 """
50 plugin = RhodeCodeAuthPlugin(plugin_id)
50 plugin = RhodeCodeAuthPlugin(plugin_id)
51 return plugin
51 return plugin
52
52
53
53
54 class JasigCasAuthnResource(AuthnPluginResourceBase):
54 class JasigCasAuthnResource(AuthnPluginResourceBase):
55 pass
55 pass
56
56
57
57
58 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
58 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
59 service_url = colander.SchemaNode(
59 service_url = colander.SchemaNode(
60 colander.String(),
60 colander.String(),
61 default='https://domain.com/cas/v1/tickets',
61 default='https://domain.com/cas/v1/tickets',
62 description=_('The url of the Jasig CAS REST service'),
62 description=_('The url of the Jasig CAS REST service'),
63 preparer=strip_whitespace,
63 preparer=strip_whitespace,
64 title=_('URL'),
64 title=_('URL'),
65 widget='string')
65 widget='string')
66
66
67
67
68 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
68 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
69
69
70 def includeme(self, config):
70 def includeme(self, config):
71 config.add_authn_plugin(self)
71 config.add_authn_plugin(self)
72 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
72 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
73 config.add_view(
73 config.add_view(
74 'rhodecode.authentication.views.AuthnPluginViewBase',
74 'rhodecode.authentication.views.AuthnPluginViewBase',
75 attr='settings_get',
75 attr='settings_get',
76 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
76 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
77 request_method='GET',
77 request_method='GET',
78 route_name='auth_home',
78 route_name='auth_home',
79 context=JasigCasAuthnResource)
79 context=JasigCasAuthnResource)
80 config.add_view(
80 config.add_view(
81 'rhodecode.authentication.views.AuthnPluginViewBase',
81 'rhodecode.authentication.views.AuthnPluginViewBase',
82 attr='settings_post',
82 attr='settings_post',
83 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
83 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
84 request_method='POST',
84 request_method='POST',
85 route_name='auth_home',
85 route_name='auth_home',
86 context=JasigCasAuthnResource)
86 context=JasigCasAuthnResource)
87
87
88 def get_settings_schema(self):
88 def get_settings_schema(self):
89 return JasigCasSettingsSchema()
89 return JasigCasSettingsSchema()
90
90
91 def get_display_name(self):
91 def get_display_name(self):
92 return _('Jasig-CAS')
92 return _('Jasig-CAS')
93
93
94 @hybrid_property
94 @hybrid_property
95 def name(self):
95 def name(self):
96 return "jasig-cas"
96 return "jasig-cas"
97
97
98 @property
98 @property
99 def is_headers_auth(self):
99 def is_headers_auth(self):
100 return True
100 return True
101
101
102 def use_fake_password(self):
102 def use_fake_password(self):
103 return True
103 return True
104
104
105 def user_activation_state(self):
105 def user_activation_state(self):
106 def_user_perms = User.get_default_user().AuthUser().permissions['global']
106 def_user_perms = User.get_default_user().AuthUser().permissions['global']
107 return 'hg.extern_activate.auto' in def_user_perms
107 return 'hg.extern_activate.auto' in def_user_perms
108
108
109 def auth(self, userobj, username, password, settings, **kwargs):
109 def auth(self, userobj, username, password, settings, **kwargs):
110 """
110 """
111 Given a user object (which may be null), username, a plaintext password,
111 Given a user object (which may be null), username, a plaintext password,
112 and a settings object (containing all the keys needed as listed in settings()),
112 and a settings object (containing all the keys needed as listed in settings()),
113 authenticate this user's login attempt.
113 authenticate this user's login attempt.
114
114
115 Return None on failure. On success, return a dictionary of the form:
115 Return None on failure. On success, return a dictionary of the form:
116
116
117 see: RhodeCodeAuthPluginBase.auth_func_attrs
117 see: RhodeCodeAuthPluginBase.auth_func_attrs
118 This is later validated for correctness
118 This is later validated for correctness
119 """
119 """
120 if not username or not password:
120 if not username or not password:
121 log.debug('Empty username or password skipping...')
121 log.debug('Empty username or password skipping...')
122 return None
122 return None
123
123
124 log.debug("Jasig CAS settings: %s", settings)
124 log.debug("Jasig CAS settings: %s", settings)
125 params = urllib.urlencode({'username': username, 'password': password})
125 params = urllib.urlencode({'username': username, 'password': password})
126 headers = {"Content-type": "application/x-www-form-urlencoded",
126 headers = {"Content-type": "application/x-www-form-urlencoded",
127 "Accept": "text/plain",
127 "Accept": "text/plain",
128 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
128 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
129 url = settings["service_url"]
129 url = settings["service_url"]
130
130
131 log.debug("Sent Jasig CAS: \n%s",
131 log.debug("Sent Jasig CAS: \n%s",
132 {"url": url, "body": params, "headers": headers})
132 {"url": url, "body": params, "headers": headers})
133 request = urllib2.Request(url, params, headers)
133 request = urllib2.Request(url, params, headers)
134 try:
134 try:
135 response = urllib2.urlopen(request)
135 response = urllib2.urlopen(request)
136 except urllib2.HTTPError as e:
136 except urllib2.HTTPError as e:
137 log.debug("HTTPError when requesting Jasig CAS (status code: %d)", e.code)
137 log.debug("HTTPError when requesting Jasig CAS (status code: %d)", e.code)
138 return None
138 return None
139 except urllib2.URLError as e:
139 except urllib2.URLError as e:
140 log.debug("URLError when requesting Jasig CAS url: %s ", url)
140 log.debug("URLError when requesting Jasig CAS url: %s ", url)
141 return None
141 return None
142
142
143 # old attrs fetched from RhodeCode database
143 # old attrs fetched from RhodeCode database
144 admin = getattr(userobj, 'admin', False)
144 admin = getattr(userobj, 'admin', False)
145 active = getattr(userobj, 'active', True)
145 active = getattr(userobj, 'active', True)
146 email = getattr(userobj, 'email', '')
146 email = getattr(userobj, 'email', '')
147 username = getattr(userobj, 'username', username)
147 username = getattr(userobj, 'username', username)
148 firstname = getattr(userobj, 'firstname', '')
148 firstname = getattr(userobj, 'firstname', '')
149 lastname = getattr(userobj, 'lastname', '')
149 lastname = getattr(userobj, 'lastname', '')
150 extern_type = getattr(userobj, 'extern_type', '')
150 extern_type = getattr(userobj, 'extern_type', '')
151
151
152 user_attrs = {
152 user_attrs = {
153 'username': username,
153 'username': username,
154 'firstname': safe_unicode(firstname or username),
154 'firstname': safe_unicode(firstname or username),
155 'lastname': safe_unicode(lastname or ''),
155 'lastname': safe_unicode(lastname or ''),
156 'groups': [],
156 'groups': [],
157 'user_group_sync': False,
157 'user_group_sync': False,
158 'email': email or '',
158 'email': email or '',
159 'admin': admin or False,
159 'admin': admin or False,
160 'active': active,
160 'active': active,
161 'active_from_extern': True,
161 'active_from_extern': True,
162 'extern_name': username,
162 'extern_name': username,
163 'extern_type': extern_type,
163 'extern_type': extern_type,
164 }
164 }
165
165
166 log.info('user `%s` authenticated correctly', user_attrs['username'])
166 log.info('user `%s` authenticated correctly', user_attrs['username'])
167 return user_attrs
167 return user_attrs
168
169
170 def includeme(config):
171 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format('jasig_cas')
172 plugin_factory(plugin_id).includeme(config)
@@ -1,528 +1,533 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication plugin for LDAP
22 RhodeCode authentication plugin for LDAP
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27
27
28 import colander
28 import colander
29 from rhodecode.translation import _
29 from rhodecode.translation import _
30 from rhodecode.authentication.base import (
30 from rhodecode.authentication.base import (
31 RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property)
31 RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property)
32 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
32 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
33 from rhodecode.authentication.routes import AuthnPluginResourceBase
33 from rhodecode.authentication.routes import AuthnPluginResourceBase
34 from rhodecode.lib.colander_utils import strip_whitespace
34 from rhodecode.lib.colander_utils import strip_whitespace
35 from rhodecode.lib.exceptions import (
35 from rhodecode.lib.exceptions import (
36 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
36 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
37 )
37 )
38 from rhodecode.lib.utils2 import safe_unicode, safe_str
38 from rhodecode.lib.utils2 import safe_unicode, safe_str
39 from rhodecode.model.db import User
39 from rhodecode.model.db import User
40 from rhodecode.model.validators import Missing
40 from rhodecode.model.validators import Missing
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 try:
44 try:
45 import ldap
45 import ldap
46 except ImportError:
46 except ImportError:
47 # means that python-ldap is not installed, we use Missing object to mark
47 # means that python-ldap is not installed, we use Missing object to mark
48 # ldap lib is Missing
48 # ldap lib is Missing
49 ldap = Missing
49 ldap = Missing
50
50
51
51
52 class LdapError(Exception):
52 class LdapError(Exception):
53 pass
53 pass
54
54
55
55
56 def plugin_factory(plugin_id, *args, **kwds):
56 def plugin_factory(plugin_id, *args, **kwds):
57 """
57 """
58 Factory function that is called during plugin discovery.
58 Factory function that is called during plugin discovery.
59 It returns the plugin instance.
59 It returns the plugin instance.
60 """
60 """
61 plugin = RhodeCodeAuthPlugin(plugin_id)
61 plugin = RhodeCodeAuthPlugin(plugin_id)
62 return plugin
62 return plugin
63
63
64
64
65 class LdapAuthnResource(AuthnPluginResourceBase):
65 class LdapAuthnResource(AuthnPluginResourceBase):
66 pass
66 pass
67
67
68
68
69 class AuthLdap(AuthLdapBase):
69 class AuthLdap(AuthLdapBase):
70 default_tls_cert_dir = '/etc/openldap/cacerts'
70 default_tls_cert_dir = '/etc/openldap/cacerts'
71
71
72 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
72 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
73 tls_kind='PLAIN', tls_reqcert='DEMAND', tls_cert_file=None,
73 tls_kind='PLAIN', tls_reqcert='DEMAND', tls_cert_file=None,
74 tls_cert_dir=None, ldap_version=3,
74 tls_cert_dir=None, ldap_version=3,
75 search_scope='SUBTREE', attr_login='uid',
75 search_scope='SUBTREE', attr_login='uid',
76 ldap_filter='', timeout=None):
76 ldap_filter='', timeout=None):
77 if ldap == Missing:
77 if ldap == Missing:
78 raise LdapImportError("Missing or incompatible ldap library")
78 raise LdapImportError("Missing or incompatible ldap library")
79
79
80 self.debug = False
80 self.debug = False
81 self.timeout = timeout or 60 * 5
81 self.timeout = timeout or 60 * 5
82 self.ldap_version = ldap_version
82 self.ldap_version = ldap_version
83 self.ldap_server_type = 'ldap'
83 self.ldap_server_type = 'ldap'
84
84
85 self.TLS_KIND = tls_kind
85 self.TLS_KIND = tls_kind
86
86
87 if self.TLS_KIND == 'LDAPS':
87 if self.TLS_KIND == 'LDAPS':
88 port = port or 689
88 port = port or 689
89 self.ldap_server_type += 's'
89 self.ldap_server_type += 's'
90
90
91 OPT_X_TLS_DEMAND = 2
91 OPT_X_TLS_DEMAND = 2
92 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, OPT_X_TLS_DEMAND)
92 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, OPT_X_TLS_DEMAND)
93 self.TLS_CERT_FILE = tls_cert_file or ''
93 self.TLS_CERT_FILE = tls_cert_file or ''
94 self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir
94 self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir
95
95
96 # split server into list
96 # split server into list
97 self.SERVER_ADDRESSES = self._get_server_list(server)
97 self.SERVER_ADDRESSES = self._get_server_list(server)
98 self.LDAP_SERVER_PORT = port
98 self.LDAP_SERVER_PORT = port
99
99
100 # USE FOR READ ONLY BIND TO LDAP SERVER
100 # USE FOR READ ONLY BIND TO LDAP SERVER
101 self.attr_login = attr_login
101 self.attr_login = attr_login
102
102
103 self.LDAP_BIND_DN = safe_str(bind_dn)
103 self.LDAP_BIND_DN = safe_str(bind_dn)
104 self.LDAP_BIND_PASS = safe_str(bind_pass)
104 self.LDAP_BIND_PASS = safe_str(bind_pass)
105
105
106 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
106 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
107 self.BASE_DN = safe_str(base_dn)
107 self.BASE_DN = safe_str(base_dn)
108 self.LDAP_FILTER = safe_str(ldap_filter)
108 self.LDAP_FILTER = safe_str(ldap_filter)
109
109
110 def _get_ldap_conn(self):
110 def _get_ldap_conn(self):
111
111
112 if self.debug:
112 if self.debug:
113 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
113 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
114
114
115 if self.TLS_CERT_FILE and hasattr(ldap, 'OPT_X_TLS_CACERTFILE'):
115 if self.TLS_CERT_FILE and hasattr(ldap, 'OPT_X_TLS_CACERTFILE'):
116 ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE)
116 ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE)
117
117
118 elif hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
118 elif hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
119 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR)
119 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR)
120
120
121 if self.TLS_KIND != 'PLAIN':
121 if self.TLS_KIND != 'PLAIN':
122 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
122 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
123
123
124 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
124 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
125 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
125 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
126
126
127 # init connection now
127 # init connection now
128 ldap_servers = self._build_servers(
128 ldap_servers = self._build_servers(
129 self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
129 self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
130 log.debug('initializing LDAP connection to:%s', ldap_servers)
130 log.debug('initializing LDAP connection to:%s', ldap_servers)
131 ldap_conn = ldap.initialize(ldap_servers)
131 ldap_conn = ldap.initialize(ldap_servers)
132 ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
132 ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
133 ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
133 ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
134 ldap_conn.timeout = self.timeout
134 ldap_conn.timeout = self.timeout
135
135
136 if self.ldap_version == 2:
136 if self.ldap_version == 2:
137 ldap_conn.protocol = ldap.VERSION2
137 ldap_conn.protocol = ldap.VERSION2
138 else:
138 else:
139 ldap_conn.protocol = ldap.VERSION3
139 ldap_conn.protocol = ldap.VERSION3
140
140
141 if self.TLS_KIND == 'START_TLS':
141 if self.TLS_KIND == 'START_TLS':
142 ldap_conn.start_tls_s()
142 ldap_conn.start_tls_s()
143
143
144 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
144 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
145 log.debug('Trying simple_bind with password and given login DN: %s',
145 log.debug('Trying simple_bind with password and given login DN: %s',
146 self.LDAP_BIND_DN)
146 self.LDAP_BIND_DN)
147 ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
147 ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
148
148
149 return ldap_conn
149 return ldap_conn
150
150
151 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
151 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
152 try:
152 try:
153 log.debug('Trying simple bind with %s', dn)
153 log.debug('Trying simple bind with %s', dn)
154 server.simple_bind_s(dn, safe_str(password))
154 server.simple_bind_s(dn, safe_str(password))
155 user = server.search_ext_s(
155 user = server.search_ext_s(
156 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
156 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
157 _, attrs = user
157 _, attrs = user
158 return attrs
158 return attrs
159
159
160 except ldap.INVALID_CREDENTIALS:
160 except ldap.INVALID_CREDENTIALS:
161 log.debug(
161 log.debug(
162 "LDAP rejected password for user '%s': %s, org_exc:",
162 "LDAP rejected password for user '%s': %s, org_exc:",
163 username, dn, exc_info=True)
163 username, dn, exc_info=True)
164
164
165 def authenticate_ldap(self, username, password):
165 def authenticate_ldap(self, username, password):
166 """
166 """
167 Authenticate a user via LDAP and return his/her LDAP properties.
167 Authenticate a user via LDAP and return his/her LDAP properties.
168
168
169 Raises AuthenticationError if the credentials are rejected, or
169 Raises AuthenticationError if the credentials are rejected, or
170 EnvironmentError if the LDAP server can't be reached.
170 EnvironmentError if the LDAP server can't be reached.
171
171
172 :param username: username
172 :param username: username
173 :param password: password
173 :param password: password
174 """
174 """
175
175
176 uid = self.get_uid(username, self.SERVER_ADDRESSES)
176 uid = self.get_uid(username, self.SERVER_ADDRESSES)
177 user_attrs = {}
177 user_attrs = {}
178 dn = ''
178 dn = ''
179
179
180 self.validate_password(username, password)
180 self.validate_password(username, password)
181 self.validate_username(username)
181 self.validate_username(username)
182
182
183 ldap_conn = None
183 ldap_conn = None
184 try:
184 try:
185 ldap_conn = self._get_ldap_conn()
185 ldap_conn = self._get_ldap_conn()
186 filter_ = '(&%s(%s=%s))' % (
186 filter_ = '(&%s(%s=%s))' % (
187 self.LDAP_FILTER, self.attr_login, username)
187 self.LDAP_FILTER, self.attr_login, username)
188 log.debug("Authenticating %r filter %s", self.BASE_DN, filter_)
188 log.debug("Authenticating %r filter %s", self.BASE_DN, filter_)
189
189
190 lobjects = ldap_conn.search_ext_s(
190 lobjects = ldap_conn.search_ext_s(
191 self.BASE_DN, self.SEARCH_SCOPE, filter_)
191 self.BASE_DN, self.SEARCH_SCOPE, filter_)
192
192
193 if not lobjects:
193 if not lobjects:
194 log.debug("No matching LDAP objects for authentication "
194 log.debug("No matching LDAP objects for authentication "
195 "of UID:'%s' username:(%s)", uid, username)
195 "of UID:'%s' username:(%s)", uid, username)
196 raise ldap.NO_SUCH_OBJECT()
196 raise ldap.NO_SUCH_OBJECT()
197
197
198 log.debug('Found matching ldap object, trying to authenticate')
198 log.debug('Found matching ldap object, trying to authenticate')
199 for (dn, _attrs) in lobjects:
199 for (dn, _attrs) in lobjects:
200 if dn is None:
200 if dn is None:
201 continue
201 continue
202
202
203 user_attrs = self.fetch_attrs_from_simple_bind(
203 user_attrs = self.fetch_attrs_from_simple_bind(
204 ldap_conn, dn, username, password)
204 ldap_conn, dn, username, password)
205 if user_attrs:
205 if user_attrs:
206 break
206 break
207 else:
207 else:
208 raise LdapPasswordError(
208 raise LdapPasswordError(
209 'Failed to authenticate user `{}`'
209 'Failed to authenticate user `{}`'
210 'with given password'.format(username))
210 'with given password'.format(username))
211
211
212 except ldap.NO_SUCH_OBJECT:
212 except ldap.NO_SUCH_OBJECT:
213 log.debug("LDAP says no such user '%s' (%s), org_exc:",
213 log.debug("LDAP says no such user '%s' (%s), org_exc:",
214 uid, username, exc_info=True)
214 uid, username, exc_info=True)
215 raise LdapUsernameError('Unable to find user')
215 raise LdapUsernameError('Unable to find user')
216 except ldap.SERVER_DOWN:
216 except ldap.SERVER_DOWN:
217 org_exc = traceback.format_exc()
217 org_exc = traceback.format_exc()
218 raise LdapConnectionError(
218 raise LdapConnectionError(
219 "LDAP can't access authentication "
219 "LDAP can't access authentication "
220 "server, org_exc:%s" % org_exc)
220 "server, org_exc:%s" % org_exc)
221 finally:
221 finally:
222 if ldap_conn:
222 if ldap_conn:
223 log.debug('ldap: connection release')
223 log.debug('ldap: connection release')
224 try:
224 try:
225 ldap_conn.unbind_s()
225 ldap_conn.unbind_s()
226 except Exception:
226 except Exception:
227 # for any reason this can raise exception we must catch it
227 # for any reason this can raise exception we must catch it
228 # to not crush the server
228 # to not crush the server
229 pass
229 pass
230
230
231 return dn, user_attrs
231 return dn, user_attrs
232
232
233
233
234 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
234 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
235 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
235 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
236 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
236 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
237 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
237 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
238
238
239 host = colander.SchemaNode(
239 host = colander.SchemaNode(
240 colander.String(),
240 colander.String(),
241 default='',
241 default='',
242 description=_('Host[s] of the LDAP Server \n'
242 description=_('Host[s] of the LDAP Server \n'
243 '(e.g., 192.168.2.154, or ldap-server.domain.com.\n '
243 '(e.g., 192.168.2.154, or ldap-server.domain.com.\n '
244 'Multiple servers can be specified using commas'),
244 'Multiple servers can be specified using commas'),
245 preparer=strip_whitespace,
245 preparer=strip_whitespace,
246 title=_('LDAP Host'),
246 title=_('LDAP Host'),
247 widget='string')
247 widget='string')
248 port = colander.SchemaNode(
248 port = colander.SchemaNode(
249 colander.Int(),
249 colander.Int(),
250 default=389,
250 default=389,
251 description=_('Custom port that the LDAP server is listening on. '
251 description=_('Custom port that the LDAP server is listening on. '
252 'Default value is: 389'),
252 'Default value is: 389'),
253 preparer=strip_whitespace,
253 preparer=strip_whitespace,
254 title=_('Port'),
254 title=_('Port'),
255 validator=colander.Range(min=0, max=65536),
255 validator=colander.Range(min=0, max=65536),
256 widget='int')
256 widget='int')
257
257
258 timeout = colander.SchemaNode(
258 timeout = colander.SchemaNode(
259 colander.Int(),
259 colander.Int(),
260 default=60 * 5,
260 default=60 * 5,
261 description=_('Timeout for LDAP connection'),
261 description=_('Timeout for LDAP connection'),
262 preparer=strip_whitespace,
262 preparer=strip_whitespace,
263 title=_('Connection timeout'),
263 title=_('Connection timeout'),
264 validator=colander.Range(min=1),
264 validator=colander.Range(min=1),
265 widget='int')
265 widget='int')
266
266
267 dn_user = colander.SchemaNode(
267 dn_user = colander.SchemaNode(
268 colander.String(),
268 colander.String(),
269 default='',
269 default='',
270 description=_('Optional user DN/account to connect to LDAP if authentication is required. \n'
270 description=_('Optional user DN/account to connect to LDAP if authentication is required. \n'
271 'e.g., cn=admin,dc=mydomain,dc=com, or '
271 'e.g., cn=admin,dc=mydomain,dc=com, or '
272 'uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com'),
272 'uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com'),
273 missing='',
273 missing='',
274 preparer=strip_whitespace,
274 preparer=strip_whitespace,
275 title=_('Account'),
275 title=_('Account'),
276 widget='string')
276 widget='string')
277 dn_pass = colander.SchemaNode(
277 dn_pass = colander.SchemaNode(
278 colander.String(),
278 colander.String(),
279 default='',
279 default='',
280 description=_('Password to authenticate for given user DN.'),
280 description=_('Password to authenticate for given user DN.'),
281 missing='',
281 missing='',
282 preparer=strip_whitespace,
282 preparer=strip_whitespace,
283 title=_('Password'),
283 title=_('Password'),
284 widget='password')
284 widget='password')
285 tls_kind = colander.SchemaNode(
285 tls_kind = colander.SchemaNode(
286 colander.String(),
286 colander.String(),
287 default=tls_kind_choices[0],
287 default=tls_kind_choices[0],
288 description=_('TLS Type'),
288 description=_('TLS Type'),
289 title=_('Connection Security'),
289 title=_('Connection Security'),
290 validator=colander.OneOf(tls_kind_choices),
290 validator=colander.OneOf(tls_kind_choices),
291 widget='select')
291 widget='select')
292 tls_reqcert = colander.SchemaNode(
292 tls_reqcert = colander.SchemaNode(
293 colander.String(),
293 colander.String(),
294 default=tls_reqcert_choices[0],
294 default=tls_reqcert_choices[0],
295 description=_('Require Cert over TLS?. Self-signed and custom '
295 description=_('Require Cert over TLS?. Self-signed and custom '
296 'certificates can be used when\n `RhodeCode Certificate` '
296 'certificates can be used when\n `RhodeCode Certificate` '
297 'found in admin > settings > system info page is extended.'),
297 'found in admin > settings > system info page is extended.'),
298 title=_('Certificate Checks'),
298 title=_('Certificate Checks'),
299 validator=colander.OneOf(tls_reqcert_choices),
299 validator=colander.OneOf(tls_reqcert_choices),
300 widget='select')
300 widget='select')
301 tls_cert_file = colander.SchemaNode(
301 tls_cert_file = colander.SchemaNode(
302 colander.String(),
302 colander.String(),
303 default='',
303 default='',
304 description=_('This specifies the PEM-format file path containing '
304 description=_('This specifies the PEM-format file path containing '
305 'certificates for use in TLS connection.\n'
305 'certificates for use in TLS connection.\n'
306 'If not specified `TLS Cert dir` will be used'),
306 'If not specified `TLS Cert dir` will be used'),
307 title=_('TLS Cert file'),
307 title=_('TLS Cert file'),
308 missing='',
308 missing='',
309 widget='string')
309 widget='string')
310 tls_cert_dir = colander.SchemaNode(
310 tls_cert_dir = colander.SchemaNode(
311 colander.String(),
311 colander.String(),
312 default=AuthLdap.default_tls_cert_dir,
312 default=AuthLdap.default_tls_cert_dir,
313 description=_('This specifies the path of a directory that contains individual '
313 description=_('This specifies the path of a directory that contains individual '
314 'CA certificates in separate files.'),
314 'CA certificates in separate files.'),
315 title=_('TLS Cert dir'),
315 title=_('TLS Cert dir'),
316 widget='string')
316 widget='string')
317 base_dn = colander.SchemaNode(
317 base_dn = colander.SchemaNode(
318 colander.String(),
318 colander.String(),
319 default='',
319 default='',
320 description=_('Base DN to search. Dynamic bind is supported. Add `$login` marker '
320 description=_('Base DN to search. Dynamic bind is supported. Add `$login` marker '
321 'in it to be replaced with current user credentials \n'
321 'in it to be replaced with current user credentials \n'
322 '(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)'),
322 '(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)'),
323 missing='',
323 missing='',
324 preparer=strip_whitespace,
324 preparer=strip_whitespace,
325 title=_('Base DN'),
325 title=_('Base DN'),
326 widget='string')
326 widget='string')
327 filter = colander.SchemaNode(
327 filter = colander.SchemaNode(
328 colander.String(),
328 colander.String(),
329 default='',
329 default='',
330 description=_('Filter to narrow results \n'
330 description=_('Filter to narrow results \n'
331 '(e.g., (&(objectCategory=Person)(objectClass=user)), or \n'
331 '(e.g., (&(objectCategory=Person)(objectClass=user)), or \n'
332 '(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))'),
332 '(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))'),
333 missing='',
333 missing='',
334 preparer=strip_whitespace,
334 preparer=strip_whitespace,
335 title=_('LDAP Search Filter'),
335 title=_('LDAP Search Filter'),
336 widget='string')
336 widget='string')
337
337
338 search_scope = colander.SchemaNode(
338 search_scope = colander.SchemaNode(
339 colander.String(),
339 colander.String(),
340 default=search_scope_choices[2],
340 default=search_scope_choices[2],
341 description=_('How deep to search LDAP. If unsure set to SUBTREE'),
341 description=_('How deep to search LDAP. If unsure set to SUBTREE'),
342 title=_('LDAP Search Scope'),
342 title=_('LDAP Search Scope'),
343 validator=colander.OneOf(search_scope_choices),
343 validator=colander.OneOf(search_scope_choices),
344 widget='select')
344 widget='select')
345 attr_login = colander.SchemaNode(
345 attr_login = colander.SchemaNode(
346 colander.String(),
346 colander.String(),
347 default='uid',
347 default='uid',
348 description=_('LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)'),
348 description=_('LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)'),
349 preparer=strip_whitespace,
349 preparer=strip_whitespace,
350 title=_('Login Attribute'),
350 title=_('Login Attribute'),
351 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
351 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
352 widget='string')
352 widget='string')
353 attr_firstname = colander.SchemaNode(
353 attr_firstname = colander.SchemaNode(
354 colander.String(),
354 colander.String(),
355 default='',
355 default='',
356 description=_('LDAP Attribute to map to first name (e.g., givenName)'),
356 description=_('LDAP Attribute to map to first name (e.g., givenName)'),
357 missing='',
357 missing='',
358 preparer=strip_whitespace,
358 preparer=strip_whitespace,
359 title=_('First Name Attribute'),
359 title=_('First Name Attribute'),
360 widget='string')
360 widget='string')
361 attr_lastname = colander.SchemaNode(
361 attr_lastname = colander.SchemaNode(
362 colander.String(),
362 colander.String(),
363 default='',
363 default='',
364 description=_('LDAP Attribute to map to last name (e.g., sn)'),
364 description=_('LDAP Attribute to map to last name (e.g., sn)'),
365 missing='',
365 missing='',
366 preparer=strip_whitespace,
366 preparer=strip_whitespace,
367 title=_('Last Name Attribute'),
367 title=_('Last Name Attribute'),
368 widget='string')
368 widget='string')
369 attr_email = colander.SchemaNode(
369 attr_email = colander.SchemaNode(
370 colander.String(),
370 colander.String(),
371 default='',
371 default='',
372 description=_('LDAP Attribute to map to email address (e.g., mail).\n'
372 description=_('LDAP Attribute to map to email address (e.g., mail).\n'
373 'Emails are a crucial part of RhodeCode. \n'
373 'Emails are a crucial part of RhodeCode. \n'
374 'If possible add a valid email attribute to ldap users.'),
374 'If possible add a valid email attribute to ldap users.'),
375 missing='',
375 missing='',
376 preparer=strip_whitespace,
376 preparer=strip_whitespace,
377 title=_('Email Attribute'),
377 title=_('Email Attribute'),
378 widget='string')
378 widget='string')
379
379
380
380
381 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
381 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
382 # used to define dynamic binding in the
382 # used to define dynamic binding in the
383 DYNAMIC_BIND_VAR = '$login'
383 DYNAMIC_BIND_VAR = '$login'
384 _settings_unsafe_keys = ['dn_pass']
384 _settings_unsafe_keys = ['dn_pass']
385
385
386 def includeme(self, config):
386 def includeme(self, config):
387 config.add_authn_plugin(self)
387 config.add_authn_plugin(self)
388 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
388 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
389 config.add_view(
389 config.add_view(
390 'rhodecode.authentication.views.AuthnPluginViewBase',
390 'rhodecode.authentication.views.AuthnPluginViewBase',
391 attr='settings_get',
391 attr='settings_get',
392 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
392 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
393 request_method='GET',
393 request_method='GET',
394 route_name='auth_home',
394 route_name='auth_home',
395 context=LdapAuthnResource)
395 context=LdapAuthnResource)
396 config.add_view(
396 config.add_view(
397 'rhodecode.authentication.views.AuthnPluginViewBase',
397 'rhodecode.authentication.views.AuthnPluginViewBase',
398 attr='settings_post',
398 attr='settings_post',
399 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
399 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
400 request_method='POST',
400 request_method='POST',
401 route_name='auth_home',
401 route_name='auth_home',
402 context=LdapAuthnResource)
402 context=LdapAuthnResource)
403
403
404 def get_settings_schema(self):
404 def get_settings_schema(self):
405 return LdapSettingsSchema()
405 return LdapSettingsSchema()
406
406
407 def get_display_name(self):
407 def get_display_name(self):
408 return _('LDAP')
408 return _('LDAP')
409
409
410 @classmethod
410 @classmethod
411 def docs(cls):
411 def docs(cls):
412 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-ldap.html"
412 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-ldap.html"
413
413
414 @hybrid_property
414 @hybrid_property
415 def name(self):
415 def name(self):
416 return "ldap"
416 return "ldap"
417
417
418 def use_fake_password(self):
418 def use_fake_password(self):
419 return True
419 return True
420
420
421 def user_activation_state(self):
421 def user_activation_state(self):
422 def_user_perms = User.get_default_user().AuthUser().permissions['global']
422 def_user_perms = User.get_default_user().AuthUser().permissions['global']
423 return 'hg.extern_activate.auto' in def_user_perms
423 return 'hg.extern_activate.auto' in def_user_perms
424
424
425 def try_dynamic_binding(self, username, password, current_args):
425 def try_dynamic_binding(self, username, password, current_args):
426 """
426 """
427 Detects marker inside our original bind, and uses dynamic auth if
427 Detects marker inside our original bind, and uses dynamic auth if
428 present
428 present
429 """
429 """
430
430
431 org_bind = current_args['bind_dn']
431 org_bind = current_args['bind_dn']
432 passwd = current_args['bind_pass']
432 passwd = current_args['bind_pass']
433
433
434 def has_bind_marker(username):
434 def has_bind_marker(username):
435 if self.DYNAMIC_BIND_VAR in username:
435 if self.DYNAMIC_BIND_VAR in username:
436 return True
436 return True
437
437
438 # we only passed in user with "special" variable
438 # we only passed in user with "special" variable
439 if org_bind and has_bind_marker(org_bind) and not passwd:
439 if org_bind and has_bind_marker(org_bind) and not passwd:
440 log.debug('Using dynamic user/password binding for ldap '
440 log.debug('Using dynamic user/password binding for ldap '
441 'authentication. Replacing `%s` with username',
441 'authentication. Replacing `%s` with username',
442 self.DYNAMIC_BIND_VAR)
442 self.DYNAMIC_BIND_VAR)
443 current_args['bind_dn'] = org_bind.replace(
443 current_args['bind_dn'] = org_bind.replace(
444 self.DYNAMIC_BIND_VAR, username)
444 self.DYNAMIC_BIND_VAR, username)
445 current_args['bind_pass'] = password
445 current_args['bind_pass'] = password
446
446
447 return current_args
447 return current_args
448
448
449 def auth(self, userobj, username, password, settings, **kwargs):
449 def auth(self, userobj, username, password, settings, **kwargs):
450 """
450 """
451 Given a user object (which may be null), username, a plaintext password,
451 Given a user object (which may be null), username, a plaintext password,
452 and a settings object (containing all the keys needed as listed in
452 and a settings object (containing all the keys needed as listed in
453 settings()), authenticate this user's login attempt.
453 settings()), authenticate this user's login attempt.
454
454
455 Return None on failure. On success, return a dictionary of the form:
455 Return None on failure. On success, return a dictionary of the form:
456
456
457 see: RhodeCodeAuthPluginBase.auth_func_attrs
457 see: RhodeCodeAuthPluginBase.auth_func_attrs
458 This is later validated for correctness
458 This is later validated for correctness
459 """
459 """
460
460
461 if not username or not password:
461 if not username or not password:
462 log.debug('Empty username or password skipping...')
462 log.debug('Empty username or password skipping...')
463 return None
463 return None
464
464
465 ldap_args = {
465 ldap_args = {
466 'server': settings.get('host', ''),
466 'server': settings.get('host', ''),
467 'base_dn': settings.get('base_dn', ''),
467 'base_dn': settings.get('base_dn', ''),
468 'port': settings.get('port'),
468 'port': settings.get('port'),
469 'bind_dn': settings.get('dn_user'),
469 'bind_dn': settings.get('dn_user'),
470 'bind_pass': settings.get('dn_pass'),
470 'bind_pass': settings.get('dn_pass'),
471 'tls_kind': settings.get('tls_kind'),
471 'tls_kind': settings.get('tls_kind'),
472 'tls_reqcert': settings.get('tls_reqcert'),
472 'tls_reqcert': settings.get('tls_reqcert'),
473 'tls_cert_file': settings.get('tls_cert_file'),
473 'tls_cert_file': settings.get('tls_cert_file'),
474 'tls_cert_dir': settings.get('tls_cert_dir'),
474 'tls_cert_dir': settings.get('tls_cert_dir'),
475 'search_scope': settings.get('search_scope'),
475 'search_scope': settings.get('search_scope'),
476 'attr_login': settings.get('attr_login'),
476 'attr_login': settings.get('attr_login'),
477 'ldap_version': 3,
477 'ldap_version': 3,
478 'ldap_filter': settings.get('filter'),
478 'ldap_filter': settings.get('filter'),
479 'timeout': settings.get('timeout')
479 'timeout': settings.get('timeout')
480 }
480 }
481
481
482 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
482 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
483
483
484 log.debug('Checking for ldap authentication.')
484 log.debug('Checking for ldap authentication.')
485
485
486 try:
486 try:
487 aldap = AuthLdap(**ldap_args)
487 aldap = AuthLdap(**ldap_args)
488 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
488 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
489 log.debug('Got ldap DN response %s', user_dn)
489 log.debug('Got ldap DN response %s', user_dn)
490
490
491 def get_ldap_attr(k):
491 def get_ldap_attr(k):
492 return ldap_attrs.get(settings.get(k), [''])[0]
492 return ldap_attrs.get(settings.get(k), [''])[0]
493
493
494 # old attrs fetched from RhodeCode database
494 # old attrs fetched from RhodeCode database
495 admin = getattr(userobj, 'admin', False)
495 admin = getattr(userobj, 'admin', False)
496 active = getattr(userobj, 'active', True)
496 active = getattr(userobj, 'active', True)
497 email = getattr(userobj, 'email', '')
497 email = getattr(userobj, 'email', '')
498 username = getattr(userobj, 'username', username)
498 username = getattr(userobj, 'username', username)
499 firstname = getattr(userobj, 'firstname', '')
499 firstname = getattr(userobj, 'firstname', '')
500 lastname = getattr(userobj, 'lastname', '')
500 lastname = getattr(userobj, 'lastname', '')
501 extern_type = getattr(userobj, 'extern_type', '')
501 extern_type = getattr(userobj, 'extern_type', '')
502
502
503 groups = []
503 groups = []
504 user_attrs = {
504 user_attrs = {
505 'username': username,
505 'username': username,
506 'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
506 'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
507 'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
507 'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
508 'groups': groups,
508 'groups': groups,
509 'user_group_sync': False,
509 'user_group_sync': False,
510 'email': get_ldap_attr('attr_email') or email,
510 'email': get_ldap_attr('attr_email') or email,
511 'admin': admin,
511 'admin': admin,
512 'active': active,
512 'active': active,
513 'active_from_extern': None,
513 'active_from_extern': None,
514 'extern_name': user_dn,
514 'extern_name': user_dn,
515 'extern_type': extern_type,
515 'extern_type': extern_type,
516 }
516 }
517
517
518 log.debug('ldap user: %s', user_attrs)
518 log.debug('ldap user: %s', user_attrs)
519 log.info('user `%s` authenticated correctly', user_attrs['username'])
519 log.info('user `%s` authenticated correctly', user_attrs['username'])
520
520
521 return user_attrs
521 return user_attrs
522
522
523 except (LdapUsernameError, LdapPasswordError, LdapImportError):
523 except (LdapUsernameError, LdapPasswordError, LdapImportError):
524 log.exception("LDAP related exception")
524 log.exception("LDAP related exception")
525 return None
525 return None
526 except (Exception,):
526 except (Exception,):
527 log.exception("Other exception")
527 log.exception("Other exception")
528 return None
528 return None
529
530
531 def includeme(config):
532 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format('ldap')
533 plugin_factory(plugin_id).includeme(config)
@@ -1,165 +1,170 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication library for PAM
22 RhodeCode authentication library for PAM
23 """
23 """
24
24
25 import colander
25 import colander
26 import grp
26 import grp
27 import logging
27 import logging
28 import pam
28 import pam
29 import pwd
29 import pwd
30 import re
30 import re
31 import socket
31 import socket
32
32
33 from rhodecode.translation import _
33 from rhodecode.translation import _
34 from rhodecode.authentication.base import (
34 from rhodecode.authentication.base import (
35 RhodeCodeExternalAuthPlugin, hybrid_property)
35 RhodeCodeExternalAuthPlugin, hybrid_property)
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 from rhodecode.lib.colander_utils import strip_whitespace
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 def plugin_factory(plugin_id, *args, **kwds):
43 def plugin_factory(plugin_id, *args, **kwds):
44 """
44 """
45 Factory function that is called during plugin discovery.
45 Factory function that is called during plugin discovery.
46 It returns the plugin instance.
46 It returns the plugin instance.
47 """
47 """
48 plugin = RhodeCodeAuthPlugin(plugin_id)
48 plugin = RhodeCodeAuthPlugin(plugin_id)
49 return plugin
49 return plugin
50
50
51
51
52 class PamAuthnResource(AuthnPluginResourceBase):
52 class PamAuthnResource(AuthnPluginResourceBase):
53 pass
53 pass
54
54
55
55
56 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
56 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
57 service = colander.SchemaNode(
57 service = colander.SchemaNode(
58 colander.String(),
58 colander.String(),
59 default='login',
59 default='login',
60 description=_('PAM service name to use for authentication.'),
60 description=_('PAM service name to use for authentication.'),
61 preparer=strip_whitespace,
61 preparer=strip_whitespace,
62 title=_('PAM service name'),
62 title=_('PAM service name'),
63 widget='string')
63 widget='string')
64 gecos = colander.SchemaNode(
64 gecos = colander.SchemaNode(
65 colander.String(),
65 colander.String(),
66 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
66 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
67 description=_('Regular expression for extracting user name/email etc. '
67 description=_('Regular expression for extracting user name/email etc. '
68 'from Unix userinfo.'),
68 'from Unix userinfo.'),
69 preparer=strip_whitespace,
69 preparer=strip_whitespace,
70 title=_('Gecos Regex'),
70 title=_('Gecos Regex'),
71 widget='string')
71 widget='string')
72
72
73
73
74 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
74 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
75 # PAM authentication can be slow. Repository operations involve a lot of
75 # PAM authentication can be slow. Repository operations involve a lot of
76 # auth calls. Little caching helps speedup push/pull operations significantly
76 # auth calls. Little caching helps speedup push/pull operations significantly
77 AUTH_CACHE_TTL = 4
77 AUTH_CACHE_TTL = 4
78
78
79 def includeme(self, config):
79 def includeme(self, config):
80 config.add_authn_plugin(self)
80 config.add_authn_plugin(self)
81 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
81 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
82 config.add_view(
82 config.add_view(
83 'rhodecode.authentication.views.AuthnPluginViewBase',
83 'rhodecode.authentication.views.AuthnPluginViewBase',
84 attr='settings_get',
84 attr='settings_get',
85 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
85 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
86 request_method='GET',
86 request_method='GET',
87 route_name='auth_home',
87 route_name='auth_home',
88 context=PamAuthnResource)
88 context=PamAuthnResource)
89 config.add_view(
89 config.add_view(
90 'rhodecode.authentication.views.AuthnPluginViewBase',
90 'rhodecode.authentication.views.AuthnPluginViewBase',
91 attr='settings_post',
91 attr='settings_post',
92 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
92 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
93 request_method='POST',
93 request_method='POST',
94 route_name='auth_home',
94 route_name='auth_home',
95 context=PamAuthnResource)
95 context=PamAuthnResource)
96
96
97 def get_display_name(self):
97 def get_display_name(self):
98 return _('PAM')
98 return _('PAM')
99
99
100 @classmethod
100 @classmethod
101 def docs(cls):
101 def docs(cls):
102 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-pam.html"
102 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-pam.html"
103
103
104 @hybrid_property
104 @hybrid_property
105 def name(self):
105 def name(self):
106 return "pam"
106 return "pam"
107
107
108 def get_settings_schema(self):
108 def get_settings_schema(self):
109 return PamSettingsSchema()
109 return PamSettingsSchema()
110
110
111 def use_fake_password(self):
111 def use_fake_password(self):
112 return True
112 return True
113
113
114 def auth(self, userobj, username, password, settings, **kwargs):
114 def auth(self, userobj, username, password, settings, **kwargs):
115 if not username or not password:
115 if not username or not password:
116 log.debug('Empty username or password skipping...')
116 log.debug('Empty username or password skipping...')
117 return None
117 return None
118 _pam = pam.pam()
118 _pam = pam.pam()
119 auth_result = _pam.authenticate(username, password, settings["service"])
119 auth_result = _pam.authenticate(username, password, settings["service"])
120
120
121 if not auth_result:
121 if not auth_result:
122 log.error("PAM was unable to authenticate user: %s", username)
122 log.error("PAM was unable to authenticate user: %s", username)
123 return None
123 return None
124
124
125 log.debug('Got PAM response %s', auth_result)
125 log.debug('Got PAM response %s', auth_result)
126
126
127 # old attrs fetched from RhodeCode database
127 # old attrs fetched from RhodeCode database
128 default_email = "%s@%s" % (username, socket.gethostname())
128 default_email = "%s@%s" % (username, socket.gethostname())
129 admin = getattr(userobj, 'admin', False)
129 admin = getattr(userobj, 'admin', False)
130 active = getattr(userobj, 'active', True)
130 active = getattr(userobj, 'active', True)
131 email = getattr(userobj, 'email', '') or default_email
131 email = getattr(userobj, 'email', '') or default_email
132 username = getattr(userobj, 'username', username)
132 username = getattr(userobj, 'username', username)
133 firstname = getattr(userobj, 'firstname', '')
133 firstname = getattr(userobj, 'firstname', '')
134 lastname = getattr(userobj, 'lastname', '')
134 lastname = getattr(userobj, 'lastname', '')
135 extern_type = getattr(userobj, 'extern_type', '')
135 extern_type = getattr(userobj, 'extern_type', '')
136
136
137 user_attrs = {
137 user_attrs = {
138 'username': username,
138 'username': username,
139 'firstname': firstname,
139 'firstname': firstname,
140 'lastname': lastname,
140 'lastname': lastname,
141 'groups': [g.gr_name for g in grp.getgrall()
141 'groups': [g.gr_name for g in grp.getgrall()
142 if username in g.gr_mem],
142 if username in g.gr_mem],
143 'user_group_sync': True,
143 'user_group_sync': True,
144 'email': email,
144 'email': email,
145 'admin': admin,
145 'admin': admin,
146 'active': active,
146 'active': active,
147 'active_from_extern': None,
147 'active_from_extern': None,
148 'extern_name': username,
148 'extern_name': username,
149 'extern_type': extern_type,
149 'extern_type': extern_type,
150 }
150 }
151
151
152 try:
152 try:
153 user_data = pwd.getpwnam(username)
153 user_data = pwd.getpwnam(username)
154 regex = settings["gecos"]
154 regex = settings["gecos"]
155 match = re.search(regex, user_data.pw_gecos)
155 match = re.search(regex, user_data.pw_gecos)
156 if match:
156 if match:
157 user_attrs["firstname"] = match.group('first_name')
157 user_attrs["firstname"] = match.group('first_name')
158 user_attrs["lastname"] = match.group('last_name')
158 user_attrs["lastname"] = match.group('last_name')
159 except Exception:
159 except Exception:
160 log.warning("Cannot extract additional info for PAM user")
160 log.warning("Cannot extract additional info for PAM user")
161 pass
161 pass
162
162
163 log.debug("pamuser: %s", user_attrs)
163 log.debug("pamuser: %s", user_attrs)
164 log.info('user `%s` authenticated correctly', user_attrs['username'])
164 log.info('user `%s` authenticated correctly', user_attrs['username'])
165 return user_attrs
165 return user_attrs
166
167
168 def includeme(config):
169 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format('pam')
170 plugin_factory(plugin_id).includeme(config)
@@ -1,143 +1,148 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 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 rhodecode.translation import _
27 from rhodecode.translation import _
28
28
29 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, hybrid_property
29 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, hybrid_property
30 from rhodecode.authentication.routes import AuthnPluginResourceBase
30 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 from rhodecode.lib.utils2 import safe_str
31 from rhodecode.lib.utils2 import safe_str
32 from rhodecode.model.db import User
32 from rhodecode.model.db import User
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 def plugin_factory(plugin_id, *args, **kwds):
37 def plugin_factory(plugin_id, *args, **kwds):
38 plugin = RhodeCodeAuthPlugin(plugin_id)
38 plugin = RhodeCodeAuthPlugin(plugin_id)
39 return plugin
39 return plugin
40
40
41
41
42 class RhodecodeAuthnResource(AuthnPluginResourceBase):
42 class RhodecodeAuthnResource(AuthnPluginResourceBase):
43 pass
43 pass
44
44
45
45
46 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
46 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
47
47
48 def includeme(self, config):
48 def includeme(self, config):
49 config.add_authn_plugin(self)
49 config.add_authn_plugin(self)
50 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
50 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
51 config.add_view(
51 config.add_view(
52 'rhodecode.authentication.views.AuthnPluginViewBase',
52 'rhodecode.authentication.views.AuthnPluginViewBase',
53 attr='settings_get',
53 attr='settings_get',
54 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
54 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
55 request_method='GET',
55 request_method='GET',
56 route_name='auth_home',
56 route_name='auth_home',
57 context=RhodecodeAuthnResource)
57 context=RhodecodeAuthnResource)
58 config.add_view(
58 config.add_view(
59 'rhodecode.authentication.views.AuthnPluginViewBase',
59 'rhodecode.authentication.views.AuthnPluginViewBase',
60 attr='settings_post',
60 attr='settings_post',
61 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
61 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
62 request_method='POST',
62 request_method='POST',
63 route_name='auth_home',
63 route_name='auth_home',
64 context=RhodecodeAuthnResource)
64 context=RhodecodeAuthnResource)
65
65
66 def get_display_name(self):
66 def get_display_name(self):
67 return _('RhodeCode Internal')
67 return _('RhodeCode Internal')
68
68
69 @hybrid_property
69 @hybrid_property
70 def name(self):
70 def name(self):
71 return "rhodecode"
71 return "rhodecode"
72
72
73 def user_activation_state(self):
73 def user_activation_state(self):
74 def_user_perms = User.get_default_user().AuthUser().permissions['global']
74 def_user_perms = User.get_default_user().AuthUser().permissions['global']
75 return 'hg.register.auto_activate' in def_user_perms
75 return 'hg.register.auto_activate' in def_user_perms
76
76
77 def allows_authentication_from(
77 def allows_authentication_from(
78 self, user, allows_non_existing_user=True,
78 self, user, allows_non_existing_user=True,
79 allowed_auth_plugins=None, allowed_auth_sources=None):
79 allowed_auth_plugins=None, allowed_auth_sources=None):
80 """
80 """
81 Custom method for this auth that doesn't accept non existing users.
81 Custom method for this auth that doesn't accept non existing users.
82 We know that user exists in our database.
82 We know that user exists in our database.
83 """
83 """
84 allows_non_existing_user = False
84 allows_non_existing_user = False
85 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
85 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
86 user, allows_non_existing_user=allows_non_existing_user)
86 user, allows_non_existing_user=allows_non_existing_user)
87
87
88 def auth(self, userobj, username, password, settings, **kwargs):
88 def auth(self, userobj, username, password, settings, **kwargs):
89 if not userobj:
89 if not userobj:
90 log.debug('userobj was:%s skipping', userobj)
90 log.debug('userobj was:%s skipping', userobj)
91 return None
91 return None
92 if userobj.extern_type != self.name:
92 if userobj.extern_type != self.name:
93 log.warning(
93 log.warning(
94 "userobj:%s extern_type mismatch got:`%s` expected:`%s`",
94 "userobj:%s extern_type mismatch got:`%s` expected:`%s`",
95 userobj, userobj.extern_type, self.name)
95 userobj, userobj.extern_type, self.name)
96 return None
96 return None
97
97
98 user_attrs = {
98 user_attrs = {
99 "username": userobj.username,
99 "username": userobj.username,
100 "firstname": userobj.firstname,
100 "firstname": userobj.firstname,
101 "lastname": userobj.lastname,
101 "lastname": userobj.lastname,
102 "groups": [],
102 "groups": [],
103 'user_group_sync': False,
103 'user_group_sync': False,
104 "email": userobj.email,
104 "email": userobj.email,
105 "admin": userobj.admin,
105 "admin": userobj.admin,
106 "active": userobj.active,
106 "active": userobj.active,
107 "active_from_extern": userobj.active,
107 "active_from_extern": userobj.active,
108 "extern_name": userobj.user_id,
108 "extern_name": userobj.user_id,
109 "extern_type": userobj.extern_type,
109 "extern_type": userobj.extern_type,
110 }
110 }
111
111
112 log.debug("User attributes:%s", user_attrs)
112 log.debug("User attributes:%s", user_attrs)
113 if userobj.active:
113 if userobj.active:
114 from rhodecode.lib import auth
114 from rhodecode.lib import auth
115 crypto_backend = auth.crypto_backend()
115 crypto_backend = auth.crypto_backend()
116 password_encoded = safe_str(password)
116 password_encoded = safe_str(password)
117 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
117 password_match, new_hash = crypto_backend.hash_check_with_upgrade(
118 password_encoded, userobj.password or '')
118 password_encoded, userobj.password or '')
119
119
120 if password_match and new_hash:
120 if password_match and new_hash:
121 log.debug('user %s properly authenticated, but '
121 log.debug('user %s properly authenticated, but '
122 'requires hash change to bcrypt', userobj)
122 'requires hash change to bcrypt', userobj)
123 # if password match, and we use OLD deprecated hash,
123 # if password match, and we use OLD deprecated hash,
124 # we should migrate this user hash password to the new hash
124 # we should migrate this user hash password to the new hash
125 # we store the new returned by hash_check_with_upgrade function
125 # we store the new returned by hash_check_with_upgrade function
126 user_attrs['_hash_migrate'] = new_hash
126 user_attrs['_hash_migrate'] = new_hash
127
127
128 if userobj.username == User.DEFAULT_USER and userobj.active:
128 if userobj.username == User.DEFAULT_USER and userobj.active:
129 log.info(
129 log.info(
130 'user `%s` authenticated correctly as anonymous user', userobj.username)
130 'user `%s` authenticated correctly as anonymous user', userobj.username)
131 return user_attrs
131 return user_attrs
132
132
133 elif userobj.username == username and password_match:
133 elif userobj.username == username and password_match:
134 log.info('user `%s` authenticated correctly', userobj.username)
134 log.info('user `%s` authenticated correctly', userobj.username)
135 return user_attrs
135 return user_attrs
136 log.warn("user `%s` used a wrong password when "
136 log.warn("user `%s` used a wrong password when "
137 "authenticating on this plugin", userobj.username)
137 "authenticating on this plugin", userobj.username)
138 return None
138 return None
139 else:
139 else:
140 log.warning(
140 log.warning(
141 'user `%s` failed to authenticate via %s, reason: account not '
141 'user `%s` failed to authenticate via %s, reason: account not '
142 'active.', username, self.name)
142 'active.', username, self.name)
143 return None
143 return None
144
145
146 def includeme(config):
147 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format('rhodecode')
148 plugin_factory(plugin_id).includeme(config)
@@ -1,151 +1,156 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication token plugin for built in internal auth
22 RhodeCode authentication token plugin for built in internal auth
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from rhodecode.translation import _
27 from rhodecode.translation import _
28 from rhodecode.authentication.base import (
28 from rhodecode.authentication.base import (
29 RhodeCodeAuthPluginBase, VCS_TYPE, hybrid_property)
29 RhodeCodeAuthPluginBase, VCS_TYPE, hybrid_property)
30 from rhodecode.authentication.routes import AuthnPluginResourceBase
30 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 from rhodecode.model.db import User, UserApiKeys, Repository
31 from rhodecode.model.db import User, UserApiKeys, Repository
32
32
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 def plugin_factory(plugin_id, *args, **kwds):
37 def plugin_factory(plugin_id, *args, **kwds):
38 plugin = RhodeCodeAuthPlugin(plugin_id)
38 plugin = RhodeCodeAuthPlugin(plugin_id)
39 return plugin
39 return plugin
40
40
41
41
42 class RhodecodeAuthnResource(AuthnPluginResourceBase):
42 class RhodecodeAuthnResource(AuthnPluginResourceBase):
43 pass
43 pass
44
44
45
45
46 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
46 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
47 """
47 """
48 Enables usage of authentication tokens for vcs operations.
48 Enables usage of authentication tokens for vcs operations.
49 """
49 """
50
50
51 def includeme(self, config):
51 def includeme(self, config):
52 config.add_authn_plugin(self)
52 config.add_authn_plugin(self)
53 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
53 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
54 config.add_view(
54 config.add_view(
55 'rhodecode.authentication.views.AuthnPluginViewBase',
55 'rhodecode.authentication.views.AuthnPluginViewBase',
56 attr='settings_get',
56 attr='settings_get',
57 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
57 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
58 request_method='GET',
58 request_method='GET',
59 route_name='auth_home',
59 route_name='auth_home',
60 context=RhodecodeAuthnResource)
60 context=RhodecodeAuthnResource)
61 config.add_view(
61 config.add_view(
62 'rhodecode.authentication.views.AuthnPluginViewBase',
62 'rhodecode.authentication.views.AuthnPluginViewBase',
63 attr='settings_post',
63 attr='settings_post',
64 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
64 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
65 request_method='POST',
65 request_method='POST',
66 route_name='auth_home',
66 route_name='auth_home',
67 context=RhodecodeAuthnResource)
67 context=RhodecodeAuthnResource)
68
68
69 def get_display_name(self):
69 def get_display_name(self):
70 return _('Rhodecode Token')
70 return _('Rhodecode Token')
71
71
72 @classmethod
72 @classmethod
73 def docs(cls):
73 def docs(cls):
74 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-token.html"
74 return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-token.html"
75
75
76 @hybrid_property
76 @hybrid_property
77 def name(self):
77 def name(self):
78 return "authtoken"
78 return "authtoken"
79
79
80 def user_activation_state(self):
80 def user_activation_state(self):
81 def_user_perms = User.get_default_user().AuthUser().permissions['global']
81 def_user_perms = User.get_default_user().AuthUser().permissions['global']
82 return 'hg.register.auto_activate' in def_user_perms
82 return 'hg.register.auto_activate' in def_user_perms
83
83
84 def allows_authentication_from(
84 def allows_authentication_from(
85 self, user, allows_non_existing_user=True,
85 self, user, allows_non_existing_user=True,
86 allowed_auth_plugins=None, allowed_auth_sources=None):
86 allowed_auth_plugins=None, allowed_auth_sources=None):
87 """
87 """
88 Custom method for this auth that doesn't accept empty users. And also
88 Custom method for this auth that doesn't accept empty users. And also
89 allows users from all other active plugins to use it and also
89 allows users from all other active plugins to use it and also
90 authenticate against it. But only via vcs mode
90 authenticate against it. But only via vcs mode
91 """
91 """
92 from rhodecode.authentication.base import get_authn_registry
92 from rhodecode.authentication.base import get_authn_registry
93 authn_registry = get_authn_registry()
93 authn_registry = get_authn_registry()
94
94
95 active_plugins = set(
95 active_plugins = set(
96 [x.name for x in authn_registry.get_plugins_for_authentication()])
96 [x.name for x in authn_registry.get_plugins_for_authentication()])
97 active_plugins.discard(self.name)
97 active_plugins.discard(self.name)
98
98
99 allowed_auth_plugins = [self.name] + list(active_plugins)
99 allowed_auth_plugins = [self.name] + list(active_plugins)
100 # only for vcs operations
100 # only for vcs operations
101 allowed_auth_sources = [VCS_TYPE]
101 allowed_auth_sources = [VCS_TYPE]
102
102
103 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
103 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
104 user, allows_non_existing_user=False,
104 user, allows_non_existing_user=False,
105 allowed_auth_plugins=allowed_auth_plugins,
105 allowed_auth_plugins=allowed_auth_plugins,
106 allowed_auth_sources=allowed_auth_sources)
106 allowed_auth_sources=allowed_auth_sources)
107
107
108 def auth(self, userobj, username, password, settings, **kwargs):
108 def auth(self, userobj, username, password, settings, **kwargs):
109 if not userobj:
109 if not userobj:
110 log.debug('userobj was:%s skipping', userobj)
110 log.debug('userobj was:%s skipping', userobj)
111 return None
111 return None
112
112
113 user_attrs = {
113 user_attrs = {
114 "username": userobj.username,
114 "username": userobj.username,
115 "firstname": userobj.firstname,
115 "firstname": userobj.firstname,
116 "lastname": userobj.lastname,
116 "lastname": userobj.lastname,
117 "groups": [],
117 "groups": [],
118 'user_group_sync': False,
118 'user_group_sync': False,
119 "email": userobj.email,
119 "email": userobj.email,
120 "admin": userobj.admin,
120 "admin": userobj.admin,
121 "active": userobj.active,
121 "active": userobj.active,
122 "active_from_extern": userobj.active,
122 "active_from_extern": userobj.active,
123 "extern_name": userobj.user_id,
123 "extern_name": userobj.user_id,
124 "extern_type": userobj.extern_type,
124 "extern_type": userobj.extern_type,
125 }
125 }
126
126
127 log.debug('Authenticating user with args %s', user_attrs)
127 log.debug('Authenticating user with args %s', user_attrs)
128 if userobj.active:
128 if userobj.active:
129 # calling context repo for token scopes
129 # calling context repo for token scopes
130 scope_repo_id = None
130 scope_repo_id = None
131 if self.acl_repo_name:
131 if self.acl_repo_name:
132 repo = Repository.get_by_repo_name(self.acl_repo_name)
132 repo = Repository.get_by_repo_name(self.acl_repo_name)
133 scope_repo_id = repo.repo_id if repo else None
133 scope_repo_id = repo.repo_id if repo else None
134
134
135 token_match = userobj.authenticate_by_token(
135 token_match = userobj.authenticate_by_token(
136 password, roles=[UserApiKeys.ROLE_VCS],
136 password, roles=[UserApiKeys.ROLE_VCS],
137 scope_repo_id=scope_repo_id)
137 scope_repo_id=scope_repo_id)
138
138
139 if userobj.username == username and token_match:
139 if userobj.username == username and token_match:
140 log.info(
140 log.info(
141 'user `%s` successfully authenticated via %s',
141 'user `%s` successfully authenticated via %s',
142 user_attrs['username'], self.name)
142 user_attrs['username'], self.name)
143 return user_attrs
143 return user_attrs
144 log.warn(
144 log.warn(
145 'user `%s` failed to authenticate via %s, reason: bad or '
145 'user `%s` failed to authenticate via %s, reason: bad or '
146 'inactive token.', username, self.name)
146 'inactive token.', username, self.name)
147 else:
147 else:
148 log.warning(
148 log.warning(
149 'user `%s` failed to authenticate via %s, reason: account not '
149 'user `%s` failed to authenticate via %s, reason: account not '
150 'active.', username, self.name)
150 'active.', username, self.name)
151 return None
151 return None
152
153
154 def includeme(config):
155 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format('token')
156 plugin_factory(plugin_id).includeme(config)
@@ -1,588 +1,588 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26 import time
26 import time
27
27
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 import pyramid.events
29 import pyramid.events
30 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response
37
37
38 from rhodecode.model import meta
38 from rhodecode.model import meta
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config import utils as config_utils
40 from rhodecode.config import utils as config_utils
41 from rhodecode.config.environment import load_pyramid_environment
41 from rhodecode.config.environment import load_pyramid_environment
42
42
43 import rhodecode.events
43 import rhodecode.events
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
46 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.celerylib.loader import configure_celery
50 from rhodecode.lib.celerylib.loader import configure_celery
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.lib.exc_tracking import store_exception
54 from rhodecode.subscribers import (
54 from rhodecode.subscribers import (
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 write_metadata_if_needed, inject_app_settings)
56 write_metadata_if_needed, inject_app_settings)
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 def is_http_error(response):
62 def is_http_error(response):
63 # error which should have traceback
63 # error which should have traceback
64 return response.status_code > 499
64 return response.status_code > 499
65
65
66
66
67 def make_pyramid_app(global_config, **settings):
67 def make_pyramid_app(global_config, **settings):
68 """
68 """
69 Constructs the WSGI application based on Pyramid.
69 Constructs the WSGI application based on Pyramid.
70
70
71 Specials:
71 Specials:
72
72
73 * The application can also be integrated like a plugin via the call to
73 * The application can also be integrated like a plugin via the call to
74 `includeme`. This is accompanied with the other utility functions which
74 `includeme`. This is accompanied with the other utility functions which
75 are called. Changing this should be done with great care to not break
75 are called. Changing this should be done with great care to not break
76 cases when these fragments are assembled from another place.
76 cases when these fragments are assembled from another place.
77
77
78 """
78 """
79
79
80 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
80 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
81 # will be replaced by the value of the environment variable "NAME" in this case.
81 # will be replaced by the value of the environment variable "NAME" in this case.
82 start_time = time.time()
82 start_time = time.time()
83
83
84 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
84 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
85
85
86 global_config = _substitute_values(global_config, environ)
86 global_config = _substitute_values(global_config, environ)
87 settings = _substitute_values(settings, environ)
87 settings = _substitute_values(settings, environ)
88
88
89 sanitize_settings_and_apply_defaults(settings)
89 sanitize_settings_and_apply_defaults(settings)
90
90
91 config = Configurator(settings=settings)
91 config = Configurator(settings=settings)
92
92
93 # Apply compatibility patches
93 # Apply compatibility patches
94 patches.inspect_getargspec()
94 patches.inspect_getargspec()
95
95
96 load_pyramid_environment(global_config, settings)
96 load_pyramid_environment(global_config, settings)
97
97
98 # Static file view comes first
98 # Static file view comes first
99 includeme_first(config)
99 includeme_first(config)
100
100
101 includeme(config)
101 includeme(config)
102
102
103 pyramid_app = config.make_wsgi_app()
103 pyramid_app = config.make_wsgi_app()
104 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
104 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
105 pyramid_app.config = config
105 pyramid_app.config = config
106
106
107 config.configure_celery(global_config['__file__'])
107 config.configure_celery(global_config['__file__'])
108 # creating the app uses a connection - return it after we are done
108 # creating the app uses a connection - return it after we are done
109 meta.Session.remove()
109 meta.Session.remove()
110 total_time = time.time() - start_time
110 total_time = time.time() - start_time
111 log.info('Pyramid app %s created and configured in %.2fs', pyramid_app, total_time)
111 log.info('Pyramid app `%s` created and configured in %.2fs',
112 pyramid_app.func_name, total_time)
112 return pyramid_app
113 return pyramid_app
113
114
114
115
115 def not_found_view(request):
116 def not_found_view(request):
116 """
117 """
117 This creates the view which should be registered as not-found-view to
118 This creates the view which should be registered as not-found-view to
118 pyramid.
119 pyramid.
119 """
120 """
120
121
121 if not getattr(request, 'vcs_call', None):
122 if not getattr(request, 'vcs_call', None):
122 # handle like regular case with our error_handler
123 # handle like regular case with our error_handler
123 return error_handler(HTTPNotFound(), request)
124 return error_handler(HTTPNotFound(), request)
124
125
125 # handle not found view as a vcs call
126 # handle not found view as a vcs call
126 settings = request.registry.settings
127 settings = request.registry.settings
127 ae_client = getattr(request, 'ae_client', None)
128 ae_client = getattr(request, 'ae_client', None)
128 vcs_app = VCSMiddleware(
129 vcs_app = VCSMiddleware(
129 HTTPNotFound(), request.registry, settings,
130 HTTPNotFound(), request.registry, settings,
130 appenlight_client=ae_client)
131 appenlight_client=ae_client)
131
132
132 return wsgiapp(vcs_app)(None, request)
133 return wsgiapp(vcs_app)(None, request)
133
134
134
135
135 def error_handler(exception, request):
136 def error_handler(exception, request):
136 import rhodecode
137 import rhodecode
137 from rhodecode.lib import helpers
138 from rhodecode.lib import helpers
138
139
139 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
140 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
140
141
141 base_response = HTTPInternalServerError()
142 base_response = HTTPInternalServerError()
142 # prefer original exception for the response since it may have headers set
143 # prefer original exception for the response since it may have headers set
143 if isinstance(exception, HTTPException):
144 if isinstance(exception, HTTPException):
144 base_response = exception
145 base_response = exception
145 elif isinstance(exception, VCSCommunicationError):
146 elif isinstance(exception, VCSCommunicationError):
146 base_response = VCSServerUnavailable()
147 base_response = VCSServerUnavailable()
147
148
148 if is_http_error(base_response):
149 if is_http_error(base_response):
149 log.exception(
150 log.exception(
150 'error occurred handling this request for path: %s', request.path)
151 'error occurred handling this request for path: %s', request.path)
151
152
152 error_explanation = base_response.explanation or str(base_response)
153 error_explanation = base_response.explanation or str(base_response)
153 if base_response.status_code == 404:
154 if base_response.status_code == 404:
154 error_explanation += " Or you don't have permission to access it."
155 error_explanation += " Or you don't have permission to access it."
155 c = AttributeDict()
156 c = AttributeDict()
156 c.error_message = base_response.status
157 c.error_message = base_response.status
157 c.error_explanation = error_explanation
158 c.error_explanation = error_explanation
158 c.visual = AttributeDict()
159 c.visual = AttributeDict()
159
160
160 c.visual.rhodecode_support_url = (
161 c.visual.rhodecode_support_url = (
161 request.registry.settings.get('rhodecode_support_url') or
162 request.registry.settings.get('rhodecode_support_url') or
162 request.route_url('rhodecode_support')
163 request.route_url('rhodecode_support')
163 )
164 )
164 c.redirect_time = 0
165 c.redirect_time = 0
165 c.rhodecode_name = rhodecode_title
166 c.rhodecode_name = rhodecode_title
166 if not c.rhodecode_name:
167 if not c.rhodecode_name:
167 c.rhodecode_name = 'Rhodecode'
168 c.rhodecode_name = 'Rhodecode'
168
169
169 c.causes = []
170 c.causes = []
170 if is_http_error(base_response):
171 if is_http_error(base_response):
171 c.causes.append('Server is overloaded.')
172 c.causes.append('Server is overloaded.')
172 c.causes.append('Server database connection is lost.')
173 c.causes.append('Server database connection is lost.')
173 c.causes.append('Server expected unhandled error.')
174 c.causes.append('Server expected unhandled error.')
174
175
175 if hasattr(base_response, 'causes'):
176 if hasattr(base_response, 'causes'):
176 c.causes = base_response.causes
177 c.causes = base_response.causes
177
178
178 c.messages = helpers.flash.pop_messages(request=request)
179 c.messages = helpers.flash.pop_messages(request=request)
179
180
180 exc_info = sys.exc_info()
181 exc_info = sys.exc_info()
181 c.exception_id = id(exc_info)
182 c.exception_id = id(exc_info)
182 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
183 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
183 or base_response.status_code > 499
184 or base_response.status_code > 499
184 c.exception_id_url = request.route_url(
185 c.exception_id_url = request.route_url(
185 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
186 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
186
187
187 if c.show_exception_id:
188 if c.show_exception_id:
188 store_exception(c.exception_id, exc_info)
189 store_exception(c.exception_id, exc_info)
189
190
190 response = render_to_response(
191 response = render_to_response(
191 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
192 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
192 response=base_response)
193 response=base_response)
193
194
194 return response
195 return response
195
196
196
197
197 def includeme_first(config):
198 def includeme_first(config):
198 # redirect automatic browser favicon.ico requests to correct place
199 # redirect automatic browser favicon.ico requests to correct place
199 def favicon_redirect(context, request):
200 def favicon_redirect(context, request):
200 return HTTPFound(
201 return HTTPFound(
201 request.static_path('rhodecode:public/images/favicon.ico'))
202 request.static_path('rhodecode:public/images/favicon.ico'))
202
203
203 config.add_view(favicon_redirect, route_name='favicon')
204 config.add_view(favicon_redirect, route_name='favicon')
204 config.add_route('favicon', '/favicon.ico')
205 config.add_route('favicon', '/favicon.ico')
205
206
206 def robots_redirect(context, request):
207 def robots_redirect(context, request):
207 return HTTPFound(
208 return HTTPFound(
208 request.static_path('rhodecode:public/robots.txt'))
209 request.static_path('rhodecode:public/robots.txt'))
209
210
210 config.add_view(robots_redirect, route_name='robots')
211 config.add_view(robots_redirect, route_name='robots')
211 config.add_route('robots', '/robots.txt')
212 config.add_route('robots', '/robots.txt')
212
213
213 config.add_static_view(
214 config.add_static_view(
214 '_static/deform', 'deform:static')
215 '_static/deform', 'deform:static')
215 config.add_static_view(
216 config.add_static_view(
216 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
217 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
217
218
218
219
219 def includeme(config):
220 def includeme(config):
220 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
221 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
221 settings = config.registry.settings
222 settings = config.registry.settings
222 config.set_request_factory(Request)
223 config.set_request_factory(Request)
223
224
224 # plugin information
225 # plugin information
225 config.registry.rhodecode_plugins = collections.OrderedDict()
226 config.registry.rhodecode_plugins = collections.OrderedDict()
226
227
227 config.add_directive(
228 config.add_directive(
228 'register_rhodecode_plugin', register_rhodecode_plugin)
229 'register_rhodecode_plugin', register_rhodecode_plugin)
229
230
230 config.add_directive('configure_celery', configure_celery)
231 config.add_directive('configure_celery', configure_celery)
231
232
232 if asbool(settings.get('appenlight', 'false')):
233 if asbool(settings.get('appenlight', 'false')):
233 config.include('appenlight_client.ext.pyramid_tween')
234 config.include('appenlight_client.ext.pyramid_tween')
234
235
235 # Includes which are required. The application would fail without them.
236 # Includes which are required. The application would fail without them.
236 config.include('pyramid_mako')
237 config.include('pyramid_mako')
237 config.include('pyramid_beaker')
238 config.include('pyramid_beaker')
238 config.include('rhodecode.lib.rc_cache')
239 config.include('rhodecode.lib.rc_cache')
239
240
240 config.include('rhodecode.authentication')
241 config.include('rhodecode.integrations')
242
243 config.include('rhodecode.apps._base.navigation')
241 config.include('rhodecode.apps._base.navigation')
244 config.include('rhodecode.apps._base.subscribers')
242 config.include('rhodecode.apps._base.subscribers')
245 config.include('rhodecode.tweens')
243 config.include('rhodecode.tweens')
246
244
245 config.include('rhodecode.integrations')
246 config.include('rhodecode.authentication')
247
247 # apps
248 # apps
248 config.include('rhodecode.apps._base')
249 config.include('rhodecode.apps._base')
249 config.include('rhodecode.apps.ops')
250 config.include('rhodecode.apps.ops')
250 config.include('rhodecode.apps.admin')
251 config.include('rhodecode.apps.admin')
251 config.include('rhodecode.apps.channelstream')
252 config.include('rhodecode.apps.channelstream')
252 config.include('rhodecode.apps.login')
253 config.include('rhodecode.apps.login')
253 config.include('rhodecode.apps.home')
254 config.include('rhodecode.apps.home')
254 config.include('rhodecode.apps.journal')
255 config.include('rhodecode.apps.journal')
255 config.include('rhodecode.apps.repository')
256 config.include('rhodecode.apps.repository')
256 config.include('rhodecode.apps.repo_group')
257 config.include('rhodecode.apps.repo_group')
257 config.include('rhodecode.apps.user_group')
258 config.include('rhodecode.apps.user_group')
258 config.include('rhodecode.apps.search')
259 config.include('rhodecode.apps.search')
259 config.include('rhodecode.apps.user_profile')
260 config.include('rhodecode.apps.user_profile')
260 config.include('rhodecode.apps.user_group_profile')
261 config.include('rhodecode.apps.user_group_profile')
261 config.include('rhodecode.apps.my_account')
262 config.include('rhodecode.apps.my_account')
262 config.include('rhodecode.apps.svn_support')
263 config.include('rhodecode.apps.svn_support')
263 config.include('rhodecode.apps.ssh_support')
264 config.include('rhodecode.apps.ssh_support')
264 config.include('rhodecode.apps.gist')
265 config.include('rhodecode.apps.gist')
265 config.include('rhodecode.apps.debug_style')
266 config.include('rhodecode.apps.debug_style')
266 config.include('rhodecode.api')
267 config.include('rhodecode.api')
267
268
268 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
269 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
269
270 config.add_translation_dirs('rhodecode:i18n/')
270 config.add_translation_dirs('rhodecode:i18n/')
271 settings['default_locale_name'] = settings.get('lang', 'en')
271 settings['default_locale_name'] = settings.get('lang', 'en')
272
272
273 # Add subscribers.
273 # Add subscribers.
274 config.add_subscriber(inject_app_settings,
274 config.add_subscriber(inject_app_settings,
275 pyramid.events.ApplicationCreated)
275 pyramid.events.ApplicationCreated)
276 config.add_subscriber(scan_repositories_if_enabled,
276 config.add_subscriber(scan_repositories_if_enabled,
277 pyramid.events.ApplicationCreated)
277 pyramid.events.ApplicationCreated)
278 config.add_subscriber(write_metadata_if_needed,
278 config.add_subscriber(write_metadata_if_needed,
279 pyramid.events.ApplicationCreated)
279 pyramid.events.ApplicationCreated)
280 config.add_subscriber(write_js_routes_if_enabled,
280 config.add_subscriber(write_js_routes_if_enabled,
281 pyramid.events.ApplicationCreated)
281 pyramid.events.ApplicationCreated)
282
282
283 # request custom methods
283 # request custom methods
284 config.add_request_method(
284 config.add_request_method(
285 'rhodecode.lib.partial_renderer.get_partial_renderer',
285 'rhodecode.lib.partial_renderer.get_partial_renderer',
286 'get_partial_renderer')
286 'get_partial_renderer')
287
287
288 # Set the authorization policy.
288 # Set the authorization policy.
289 authz_policy = ACLAuthorizationPolicy()
289 authz_policy = ACLAuthorizationPolicy()
290 config.set_authorization_policy(authz_policy)
290 config.set_authorization_policy(authz_policy)
291
291
292 # Set the default renderer for HTML templates to mako.
292 # Set the default renderer for HTML templates to mako.
293 config.add_mako_renderer('.html')
293 config.add_mako_renderer('.html')
294
294
295 config.add_renderer(
295 config.add_renderer(
296 name='json_ext',
296 name='json_ext',
297 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
297 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
298
298
299 # include RhodeCode plugins
299 # include RhodeCode plugins
300 includes = aslist(settings.get('rhodecode.includes', []))
300 includes = aslist(settings.get('rhodecode.includes', []))
301 for inc in includes:
301 for inc in includes:
302 config.include(inc)
302 config.include(inc)
303
303
304 # custom not found view, if our pyramid app doesn't know how to handle
304 # custom not found view, if our pyramid app doesn't know how to handle
305 # the request pass it to potential VCS handling ap
305 # the request pass it to potential VCS handling ap
306 config.add_notfound_view(not_found_view)
306 config.add_notfound_view(not_found_view)
307 if not settings.get('debugtoolbar.enabled', False):
307 if not settings.get('debugtoolbar.enabled', False):
308 # disabled debugtoolbar handle all exceptions via the error_handlers
308 # disabled debugtoolbar handle all exceptions via the error_handlers
309 config.add_view(error_handler, context=Exception)
309 config.add_view(error_handler, context=Exception)
310
310
311 # all errors including 403/404/50X
311 # all errors including 403/404/50X
312 config.add_view(error_handler, context=HTTPError)
312 config.add_view(error_handler, context=HTTPError)
313
313
314
314
315 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
315 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
316 """
316 """
317 Apply outer WSGI middlewares around the application.
317 Apply outer WSGI middlewares around the application.
318 """
318 """
319 registry = config.registry
319 registry = config.registry
320 settings = registry.settings
320 settings = registry.settings
321
321
322 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
322 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
323 pyramid_app = HttpsFixup(pyramid_app, settings)
323 pyramid_app = HttpsFixup(pyramid_app, settings)
324
324
325 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
325 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
326 pyramid_app, settings)
326 pyramid_app, settings)
327 registry.ae_client = _ae_client
327 registry.ae_client = _ae_client
328
328
329 if settings['gzip_responses']:
329 if settings['gzip_responses']:
330 pyramid_app = make_gzip_middleware(
330 pyramid_app = make_gzip_middleware(
331 pyramid_app, settings, compress_level=1)
331 pyramid_app, settings, compress_level=1)
332
332
333 # this should be the outer most middleware in the wsgi stack since
333 # this should be the outer most middleware in the wsgi stack since
334 # middleware like Routes make database calls
334 # middleware like Routes make database calls
335 def pyramid_app_with_cleanup(environ, start_response):
335 def pyramid_app_with_cleanup(environ, start_response):
336 try:
336 try:
337 return pyramid_app(environ, start_response)
337 return pyramid_app(environ, start_response)
338 finally:
338 finally:
339 # Dispose current database session and rollback uncommitted
339 # Dispose current database session and rollback uncommitted
340 # transactions.
340 # transactions.
341 meta.Session.remove()
341 meta.Session.remove()
342
342
343 # In a single threaded mode server, on non sqlite db we should have
343 # In a single threaded mode server, on non sqlite db we should have
344 # '0 Current Checked out connections' at the end of a request,
344 # '0 Current Checked out connections' at the end of a request,
345 # if not, then something, somewhere is leaving a connection open
345 # if not, then something, somewhere is leaving a connection open
346 pool = meta.Base.metadata.bind.engine.pool
346 pool = meta.Base.metadata.bind.engine.pool
347 log.debug('sa pool status: %s', pool.status())
347 log.debug('sa pool status: %s', pool.status())
348 log.debug('Request processing finalized')
348 log.debug('Request processing finalized')
349
349
350 return pyramid_app_with_cleanup
350 return pyramid_app_with_cleanup
351
351
352
352
353 def sanitize_settings_and_apply_defaults(settings):
353 def sanitize_settings_and_apply_defaults(settings):
354 """
354 """
355 Applies settings defaults and does all type conversion.
355 Applies settings defaults and does all type conversion.
356
356
357 We would move all settings parsing and preparation into this place, so that
357 We would move all settings parsing and preparation into this place, so that
358 we have only one place left which deals with this part. The remaining parts
358 we have only one place left which deals with this part. The remaining parts
359 of the application would start to rely fully on well prepared settings.
359 of the application would start to rely fully on well prepared settings.
360
360
361 This piece would later be split up per topic to avoid a big fat monster
361 This piece would later be split up per topic to avoid a big fat monster
362 function.
362 function.
363 """
363 """
364
364
365 settings.setdefault('rhodecode.edition', 'Community Edition')
365 settings.setdefault('rhodecode.edition', 'Community Edition')
366
366
367 if 'mako.default_filters' not in settings:
367 if 'mako.default_filters' not in settings:
368 # set custom default filters if we don't have it defined
368 # set custom default filters if we don't have it defined
369 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
369 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
370 settings['mako.default_filters'] = 'h_filter'
370 settings['mako.default_filters'] = 'h_filter'
371
371
372 if 'mako.directories' not in settings:
372 if 'mako.directories' not in settings:
373 mako_directories = settings.setdefault('mako.directories', [
373 mako_directories = settings.setdefault('mako.directories', [
374 # Base templates of the original application
374 # Base templates of the original application
375 'rhodecode:templates',
375 'rhodecode:templates',
376 ])
376 ])
377 log.debug(
377 log.debug(
378 "Using the following Mako template directories: %s",
378 "Using the following Mako template directories: %s",
379 mako_directories)
379 mako_directories)
380
380
381 # Default includes, possible to change as a user
381 # Default includes, possible to change as a user
382 pyramid_includes = settings.setdefault('pyramid.includes', [
382 pyramid_includes = settings.setdefault('pyramid.includes', [
383 'rhodecode.lib.middleware.request_wrapper',
383 'rhodecode.lib.middleware.request_wrapper',
384 ])
384 ])
385 log.debug(
385 log.debug(
386 "Using the following pyramid.includes: %s",
386 "Using the following pyramid.includes: %s",
387 pyramid_includes)
387 pyramid_includes)
388
388
389 # TODO: johbo: Re-think this, usually the call to config.include
389 # TODO: johbo: Re-think this, usually the call to config.include
390 # should allow to pass in a prefix.
390 # should allow to pass in a prefix.
391 settings.setdefault('rhodecode.api.url', '/_admin/api')
391 settings.setdefault('rhodecode.api.url', '/_admin/api')
392
392
393 # Sanitize generic settings.
393 # Sanitize generic settings.
394 _list_setting(settings, 'default_encoding', 'UTF-8')
394 _list_setting(settings, 'default_encoding', 'UTF-8')
395 _bool_setting(settings, 'is_test', 'false')
395 _bool_setting(settings, 'is_test', 'false')
396 _bool_setting(settings, 'gzip_responses', 'false')
396 _bool_setting(settings, 'gzip_responses', 'false')
397
397
398 # Call split out functions that sanitize settings for each topic.
398 # Call split out functions that sanitize settings for each topic.
399 _sanitize_appenlight_settings(settings)
399 _sanitize_appenlight_settings(settings)
400 _sanitize_vcs_settings(settings)
400 _sanitize_vcs_settings(settings)
401 _sanitize_cache_settings(settings)
401 _sanitize_cache_settings(settings)
402
402
403 # configure instance id
403 # configure instance id
404 config_utils.set_instance_id(settings)
404 config_utils.set_instance_id(settings)
405
405
406 return settings
406 return settings
407
407
408
408
409 def _sanitize_appenlight_settings(settings):
409 def _sanitize_appenlight_settings(settings):
410 _bool_setting(settings, 'appenlight', 'false')
410 _bool_setting(settings, 'appenlight', 'false')
411
411
412
412
413 def _sanitize_vcs_settings(settings):
413 def _sanitize_vcs_settings(settings):
414 """
414 """
415 Applies settings defaults and does type conversion for all VCS related
415 Applies settings defaults and does type conversion for all VCS related
416 settings.
416 settings.
417 """
417 """
418 _string_setting(settings, 'vcs.svn.compatible_version', '')
418 _string_setting(settings, 'vcs.svn.compatible_version', '')
419 _string_setting(settings, 'git_rev_filter', '--all')
419 _string_setting(settings, 'git_rev_filter', '--all')
420 _string_setting(settings, 'vcs.hooks.protocol', 'http')
420 _string_setting(settings, 'vcs.hooks.protocol', 'http')
421 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
421 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
422 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
422 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
423 _string_setting(settings, 'vcs.server', '')
423 _string_setting(settings, 'vcs.server', '')
424 _string_setting(settings, 'vcs.server.log_level', 'debug')
424 _string_setting(settings, 'vcs.server.log_level', 'debug')
425 _string_setting(settings, 'vcs.server.protocol', 'http')
425 _string_setting(settings, 'vcs.server.protocol', 'http')
426 _bool_setting(settings, 'startup.import_repos', 'false')
426 _bool_setting(settings, 'startup.import_repos', 'false')
427 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
427 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
428 _bool_setting(settings, 'vcs.server.enable', 'true')
428 _bool_setting(settings, 'vcs.server.enable', 'true')
429 _bool_setting(settings, 'vcs.start_server', 'false')
429 _bool_setting(settings, 'vcs.start_server', 'false')
430 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
430 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
431 _int_setting(settings, 'vcs.connection_timeout', 3600)
431 _int_setting(settings, 'vcs.connection_timeout', 3600)
432
432
433 # Support legacy values of vcs.scm_app_implementation. Legacy
433 # Support legacy values of vcs.scm_app_implementation. Legacy
434 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
434 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
435 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
435 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
436 scm_app_impl = settings['vcs.scm_app_implementation']
436 scm_app_impl = settings['vcs.scm_app_implementation']
437 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
437 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
438 settings['vcs.scm_app_implementation'] = 'http'
438 settings['vcs.scm_app_implementation'] = 'http'
439
439
440
440
441 def _sanitize_cache_settings(settings):
441 def _sanitize_cache_settings(settings):
442 temp_store = tempfile.gettempdir()
442 temp_store = tempfile.gettempdir()
443 default_cache_dir = os.path.join(temp_store, 'rc_cache')
443 default_cache_dir = os.path.join(temp_store, 'rc_cache')
444
444
445 # save default, cache dir, and use it for all backends later.
445 # save default, cache dir, and use it for all backends later.
446 default_cache_dir = _string_setting(
446 default_cache_dir = _string_setting(
447 settings,
447 settings,
448 'cache_dir',
448 'cache_dir',
449 default_cache_dir, lower=False, default_when_empty=True)
449 default_cache_dir, lower=False, default_when_empty=True)
450
450
451 # ensure we have our dir created
451 # ensure we have our dir created
452 if not os.path.isdir(default_cache_dir):
452 if not os.path.isdir(default_cache_dir):
453 os.makedirs(default_cache_dir, mode=0755)
453 os.makedirs(default_cache_dir, mode=0755)
454
454
455 # exception store cache
455 # exception store cache
456 _string_setting(
456 _string_setting(
457 settings,
457 settings,
458 'exception_tracker.store_path',
458 'exception_tracker.store_path',
459 temp_store, lower=False, default_when_empty=True)
459 temp_store, lower=False, default_when_empty=True)
460
460
461 # cache_perms
461 # cache_perms
462 _string_setting(
462 _string_setting(
463 settings,
463 settings,
464 'rc_cache.cache_perms.backend',
464 'rc_cache.cache_perms.backend',
465 'dogpile.cache.rc.file_namespace', lower=False)
465 'dogpile.cache.rc.file_namespace', lower=False)
466 _int_setting(
466 _int_setting(
467 settings,
467 settings,
468 'rc_cache.cache_perms.expiration_time',
468 'rc_cache.cache_perms.expiration_time',
469 60)
469 60)
470 _string_setting(
470 _string_setting(
471 settings,
471 settings,
472 'rc_cache.cache_perms.arguments.filename',
472 'rc_cache.cache_perms.arguments.filename',
473 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
473 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
474
474
475 # cache_repo
475 # cache_repo
476 _string_setting(
476 _string_setting(
477 settings,
477 settings,
478 'rc_cache.cache_repo.backend',
478 'rc_cache.cache_repo.backend',
479 'dogpile.cache.rc.file_namespace', lower=False)
479 'dogpile.cache.rc.file_namespace', lower=False)
480 _int_setting(
480 _int_setting(
481 settings,
481 settings,
482 'rc_cache.cache_repo.expiration_time',
482 'rc_cache.cache_repo.expiration_time',
483 60)
483 60)
484 _string_setting(
484 _string_setting(
485 settings,
485 settings,
486 'rc_cache.cache_repo.arguments.filename',
486 'rc_cache.cache_repo.arguments.filename',
487 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
487 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
488
488
489 # cache_license
489 # cache_license
490 _string_setting(
490 _string_setting(
491 settings,
491 settings,
492 'rc_cache.cache_license.backend',
492 'rc_cache.cache_license.backend',
493 'dogpile.cache.rc.file_namespace', lower=False)
493 'dogpile.cache.rc.file_namespace', lower=False)
494 _int_setting(
494 _int_setting(
495 settings,
495 settings,
496 'rc_cache.cache_license.expiration_time',
496 'rc_cache.cache_license.expiration_time',
497 5*60)
497 5*60)
498 _string_setting(
498 _string_setting(
499 settings,
499 settings,
500 'rc_cache.cache_license.arguments.filename',
500 'rc_cache.cache_license.arguments.filename',
501 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
501 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
502
502
503 # cache_repo_longterm memory, 96H
503 # cache_repo_longterm memory, 96H
504 _string_setting(
504 _string_setting(
505 settings,
505 settings,
506 'rc_cache.cache_repo_longterm.backend',
506 'rc_cache.cache_repo_longterm.backend',
507 'dogpile.cache.rc.memory_lru', lower=False)
507 'dogpile.cache.rc.memory_lru', lower=False)
508 _int_setting(
508 _int_setting(
509 settings,
509 settings,
510 'rc_cache.cache_repo_longterm.expiration_time',
510 'rc_cache.cache_repo_longterm.expiration_time',
511 345600)
511 345600)
512 _int_setting(
512 _int_setting(
513 settings,
513 settings,
514 'rc_cache.cache_repo_longterm.max_size',
514 'rc_cache.cache_repo_longterm.max_size',
515 10000)
515 10000)
516
516
517 # sql_cache_short
517 # sql_cache_short
518 _string_setting(
518 _string_setting(
519 settings,
519 settings,
520 'rc_cache.sql_cache_short.backend',
520 'rc_cache.sql_cache_short.backend',
521 'dogpile.cache.rc.memory_lru', lower=False)
521 'dogpile.cache.rc.memory_lru', lower=False)
522 _int_setting(
522 _int_setting(
523 settings,
523 settings,
524 'rc_cache.sql_cache_short.expiration_time',
524 'rc_cache.sql_cache_short.expiration_time',
525 30)
525 30)
526 _int_setting(
526 _int_setting(
527 settings,
527 settings,
528 'rc_cache.sql_cache_short.max_size',
528 'rc_cache.sql_cache_short.max_size',
529 10000)
529 10000)
530
530
531
531
532 def _int_setting(settings, name, default):
532 def _int_setting(settings, name, default):
533 settings[name] = int(settings.get(name, default))
533 settings[name] = int(settings.get(name, default))
534 return settings[name]
534 return settings[name]
535
535
536
536
537 def _bool_setting(settings, name, default):
537 def _bool_setting(settings, name, default):
538 input_val = settings.get(name, default)
538 input_val = settings.get(name, default)
539 if isinstance(input_val, unicode):
539 if isinstance(input_val, unicode):
540 input_val = input_val.encode('utf8')
540 input_val = input_val.encode('utf8')
541 settings[name] = asbool(input_val)
541 settings[name] = asbool(input_val)
542 return settings[name]
542 return settings[name]
543
543
544
544
545 def _list_setting(settings, name, default):
545 def _list_setting(settings, name, default):
546 raw_value = settings.get(name, default)
546 raw_value = settings.get(name, default)
547
547
548 old_separator = ','
548 old_separator = ','
549 if old_separator in raw_value:
549 if old_separator in raw_value:
550 # If we get a comma separated list, pass it to our own function.
550 # If we get a comma separated list, pass it to our own function.
551 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
551 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
552 else:
552 else:
553 # Otherwise we assume it uses pyramids space/newline separation.
553 # Otherwise we assume it uses pyramids space/newline separation.
554 settings[name] = aslist(raw_value)
554 settings[name] = aslist(raw_value)
555 return settings[name]
555 return settings[name]
556
556
557
557
558 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
558 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
559 value = settings.get(name, default)
559 value = settings.get(name, default)
560
560
561 if default_when_empty and not value:
561 if default_when_empty and not value:
562 # use default value when value is empty
562 # use default value when value is empty
563 value = default
563 value = default
564
564
565 if lower:
565 if lower:
566 value = value.lower()
566 value = value.lower()
567 settings[name] = value
567 settings[name] = value
568 return settings[name]
568 return settings[name]
569
569
570
570
571 def _substitute_values(mapping, substitutions):
571 def _substitute_values(mapping, substitutions):
572
572
573 try:
573 try:
574 result = {
574 result = {
575 # Note: Cannot use regular replacements, since they would clash
575 # Note: Cannot use regular replacements, since they would clash
576 # with the implementation of ConfigParser. Using "format" instead.
576 # with the implementation of ConfigParser. Using "format" instead.
577 key: value.format(**substitutions)
577 key: value.format(**substitutions)
578 for key, value in mapping.items()
578 for key, value in mapping.items()
579 }
579 }
580 except KeyError as e:
580 except KeyError as e:
581 raise ValueError(
581 raise ValueError(
582 'Failed to substitute env variable: {}. '
582 'Failed to substitute env variable: {}. '
583 'Make sure you have specified this env variable without ENV_ prefix'.format(e))
583 'Make sure you have specified this env variable without ENV_ prefix'.format(e))
584 except ValueError as e:
584 except ValueError as e:
585 log.warning('Failed to substitute ENV variable: %s', e)
585 log.warning('Failed to substitute ENV variable: %s', e)
586 result = mapping
586 result = mapping
587
587
588 return result
588 return result
@@ -1,37 +1,37 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2012-2018 RhodeCode GmbH
2 # Copyright (C) 2012-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import logging
20 import logging
21 import collections
21 import collections
22
22
23 log = logging.getLogger(__name__)
23 log = logging.getLogger(__name__)
24
24
25
25
26 class IntegrationTypeRegistry(collections.OrderedDict):
26 class IntegrationTypeRegistry(collections.OrderedDict):
27 """
27 """
28 Registry Class to hold IntegrationTypes
28 Registry Class to hold IntegrationTypes
29 """
29 """
30 def register_integration_type(self, IntegrationType):
30 def register_integration_type(self, IntegrationType):
31 key = IntegrationType.key
31 key = IntegrationType.key
32 if key in self:
32 if key in self:
33 log.debug(
33 log.debug(
34 'Overriding existing integration type %s (%s) with %s',
34 'Overriding existing integration type %s (%s) with %s',
35 self[key], key, IntegrationType)
35 self[key].__class__, key, IntegrationType)
36
36
37 self[key] = IntegrationType
37 self[key] = IntegrationType
@@ -1,189 +1,180 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 # Import early to make sure things are patched up properly
21 # Import early to make sure things are patched up properly
22 from setuptools import setup, find_packages
22 from setuptools import setup, find_packages
23
23
24 import os
24 import os
25 import sys
25 import sys
26 import pkgutil
26 import pkgutil
27 import platform
27 import platform
28 import codecs
28 import codecs
29
29
30 try: # for pip >= 10
30 try: # for pip >= 10
31 from pip._internal.req import parse_requirements
31 from pip._internal.req import parse_requirements
32 except ImportError: # for pip <= 9.0.3
32 except ImportError: # for pip <= 9.0.3
33 from pip.req import parse_requirements
33 from pip.req import parse_requirements
34
34
35 try: # for pip >= 10
35 try: # for pip >= 10
36 from pip._internal.download import PipSession
36 from pip._internal.download import PipSession
37 except ImportError: # for pip <= 9.0.3
37 except ImportError: # for pip <= 9.0.3
38 from pip.download import PipSession
38 from pip.download import PipSession
39
39
40
40
41 if sys.version_info < (2, 7):
41 if sys.version_info < (2, 7):
42 raise Exception('RhodeCode requires Python 2.7 or later')
42 raise Exception('RhodeCode requires Python 2.7 or later')
43
43
44 here = os.path.abspath(os.path.dirname(__file__))
44 here = os.path.abspath(os.path.dirname(__file__))
45
45
46 # defines current platform
46 # defines current platform
47 __platform__ = platform.system()
47 __platform__ = platform.system()
48 __license__ = 'AGPLv3, and Commercial License'
48 __license__ = 'AGPLv3, and Commercial License'
49 __author__ = 'RhodeCode GmbH'
49 __author__ = 'RhodeCode GmbH'
50 __url__ = 'https://code.rhodecode.com'
50 __url__ = 'https://code.rhodecode.com'
51 is_windows = __platform__ in ('Windows',)
51 is_windows = __platform__ in ('Windows',)
52
52
53
53
54 def _get_requirements(req_filename, exclude=None, extras=None):
54 def _get_requirements(req_filename, exclude=None, extras=None):
55 extras = extras or []
55 extras = extras or []
56 exclude = exclude or []
56 exclude = exclude or []
57
57
58 try:
58 try:
59 parsed = parse_requirements(
59 parsed = parse_requirements(
60 os.path.join(here, req_filename), session=PipSession())
60 os.path.join(here, req_filename), session=PipSession())
61 except TypeError:
61 except TypeError:
62 # try pip < 6.0.0, that doesn't support session
62 # try pip < 6.0.0, that doesn't support session
63 parsed = parse_requirements(os.path.join(here, req_filename))
63 parsed = parse_requirements(os.path.join(here, req_filename))
64
64
65 requirements = []
65 requirements = []
66 for ir in parsed:
66 for ir in parsed:
67 if ir.req and ir.name not in exclude:
67 if ir.req and ir.name not in exclude:
68 requirements.append(str(ir.req))
68 requirements.append(str(ir.req))
69 return requirements + extras
69 return requirements + extras
70
70
71
71
72 # requirements extract
72 # requirements extract
73 setup_requirements = ['PasteScript', 'pytest-runner']
73 setup_requirements = ['PasteScript', 'pytest-runner']
74 install_requirements = _get_requirements(
74 install_requirements = _get_requirements(
75 'requirements.txt', exclude=['setuptools', 'entrypoints'])
75 'requirements.txt', exclude=['setuptools', 'entrypoints'])
76 test_requirements = _get_requirements(
76 test_requirements = _get_requirements(
77 'requirements_test.txt', extras=['configobj'])
77 'requirements_test.txt', extras=['configobj'])
78
78
79
79
80 def get_version():
80 def get_version():
81 version = pkgutil.get_data('rhodecode', 'VERSION')
81 version = pkgutil.get_data('rhodecode', 'VERSION')
82 return version.strip()
82 return version.strip()
83
83
84
84
85 # additional files that goes into package itself
85 # additional files that goes into package itself
86 package_data = {
86 package_data = {
87 '': ['*.txt', '*.rst'],
87 '': ['*.txt', '*.rst'],
88 'configs': ['*.ini'],
88 'configs': ['*.ini'],
89 'rhodecode': ['VERSION', 'i18n/*/LC_MESSAGES/*.mo', ],
89 'rhodecode': ['VERSION', 'i18n/*/LC_MESSAGES/*.mo', ],
90 }
90 }
91
91
92 description = 'Source Code Management Platform'
92 description = 'Source Code Management Platform'
93 keywords = ' '.join([
93 keywords = ' '.join([
94 'rhodecode', 'mercurial', 'git', 'svn',
94 'rhodecode', 'mercurial', 'git', 'svn',
95 'code review',
95 'code review',
96 'repo groups', 'ldap', 'repository management', 'hgweb',
96 'repo groups', 'ldap', 'repository management', 'hgweb',
97 'hgwebdir', 'gitweb', 'serving hgweb',
97 'hgwebdir', 'gitweb', 'serving hgweb',
98 ])
98 ])
99
99
100
100
101 # README/DESCRIPTION generation
101 # README/DESCRIPTION generation
102 readme_file = 'README.rst'
102 readme_file = 'README.rst'
103 changelog_file = 'CHANGES.rst'
103 changelog_file = 'CHANGES.rst'
104 try:
104 try:
105 long_description = codecs.open(readme_file).read() + '\n\n' + \
105 long_description = codecs.open(readme_file).read() + '\n\n' + \
106 codecs.open(changelog_file).read()
106 codecs.open(changelog_file).read()
107 except IOError as err:
107 except IOError as err:
108 sys.stderr.write(
108 sys.stderr.write(
109 "[WARNING] Cannot find file specified as long_description (%s)\n "
109 "[WARNING] Cannot find file specified as long_description (%s)\n "
110 "or changelog (%s) skipping that file" % (readme_file, changelog_file))
110 "or changelog (%s) skipping that file" % (readme_file, changelog_file))
111 long_description = description
111 long_description = description
112
112
113
113
114 setup(
114 setup(
115 name='rhodecode-enterprise-ce',
115 name='rhodecode-enterprise-ce',
116 version=get_version(),
116 version=get_version(),
117 description=description,
117 description=description,
118 long_description=long_description,
118 long_description=long_description,
119 keywords=keywords,
119 keywords=keywords,
120 license=__license__,
120 license=__license__,
121 author=__author__,
121 author=__author__,
122 author_email='support@rhodecode.com',
122 author_email='support@rhodecode.com',
123 url=__url__,
123 url=__url__,
124 setup_requires=setup_requirements,
124 setup_requires=setup_requirements,
125 install_requires=install_requirements,
125 install_requires=install_requirements,
126 tests_require=test_requirements,
126 tests_require=test_requirements,
127 zip_safe=False,
127 zip_safe=False,
128 packages=find_packages(exclude=["docs", "tests*"]),
128 packages=find_packages(exclude=["docs", "tests*"]),
129 package_data=package_data,
129 package_data=package_data,
130 include_package_data=True,
130 include_package_data=True,
131 classifiers=[
131 classifiers=[
132 'Development Status :: 6 - Mature',
132 'Development Status :: 6 - Mature',
133 'Environment :: Web Environment',
133 'Environment :: Web Environment',
134 'Intended Audience :: Developers',
134 'Intended Audience :: Developers',
135 'Operating System :: OS Independent',
135 'Operating System :: OS Independent',
136 'Topic :: Software Development :: Version Control',
136 'Topic :: Software Development :: Version Control',
137 'License :: OSI Approved :: Affero GNU General Public License v3 or later (AGPLv3+)',
137 'License :: OSI Approved :: Affero GNU General Public License v3 or later (AGPLv3+)',
138 'Programming Language :: Python :: 2.7',
138 'Programming Language :: Python :: 2.7',
139 ],
139 ],
140 message_extractors={
140 message_extractors={
141 'rhodecode': [
141 'rhodecode': [
142 ('**.py', 'python', None),
142 ('**.py', 'python', None),
143 ('**.js', 'javascript', None),
143 ('**.js', 'javascript', None),
144 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
144 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
145 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
145 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
146 ('public/**', 'ignore', None),
146 ('public/**', 'ignore', None),
147 ]
147 ]
148 },
148 },
149 paster_plugins=['PasteScript'],
149 paster_plugins=['PasteScript'],
150 entry_points={
150 entry_points={
151 'enterprise.plugins1': [
152 'crowd=rhodecode.authentication.plugins.auth_crowd:plugin_factory',
153 'headers=rhodecode.authentication.plugins.auth_headers:plugin_factory',
154 'jasig_cas=rhodecode.authentication.plugins.auth_jasig_cas:plugin_factory',
155 'ldap=rhodecode.authentication.plugins.auth_ldap:plugin_factory',
156 'pam=rhodecode.authentication.plugins.auth_pam:plugin_factory',
157 'rhodecode=rhodecode.authentication.plugins.auth_rhodecode:plugin_factory',
158 'token=rhodecode.authentication.plugins.auth_token:plugin_factory',
159 ],
160 'paste.app_factory': [
151 'paste.app_factory': [
161 'main=rhodecode.config.middleware:make_pyramid_app',
152 'main=rhodecode.config.middleware:make_pyramid_app',
162 ],
153 ],
163 'paste.global_paster_command': [
154 'paste.global_paster_command': [
164 'ishell=rhodecode.lib.paster_commands.ishell:Command',
155 'ishell=rhodecode.lib.paster_commands.ishell:Command',
165 'upgrade-db=rhodecode.lib.paster_commands.upgrade_db:UpgradeDb',
156 'upgrade-db=rhodecode.lib.paster_commands.upgrade_db:UpgradeDb',
166
157
167 'setup-rhodecode=rhodecode.lib.paster_commands.deprecated.setup_rhodecode:Command',
158 'setup-rhodecode=rhodecode.lib.paster_commands.deprecated.setup_rhodecode:Command',
168 'celeryd=rhodecode.lib.paster_commands.deprecated.celeryd:Command',
159 'celeryd=rhodecode.lib.paster_commands.deprecated.celeryd:Command',
169 ],
160 ],
170 'pyramid.pshell_runner': [
161 'pyramid.pshell_runner': [
171 'ipython = rhodecode.lib.pyramid_shell:ipython_shell_runner',
162 'ipython = rhodecode.lib.pyramid_shell:ipython_shell_runner',
172 ],
163 ],
173 'pytest11': [
164 'pytest11': [
174 'pylons=rhodecode.tests.pylons_plugin',
165 'pylons=rhodecode.tests.pylons_plugin',
175 'enterprise=rhodecode.tests.plugin',
166 'enterprise=rhodecode.tests.plugin',
176 ],
167 ],
177 'console_scripts': [
168 'console_scripts': [
178 'rc-server=rhodecode.rcserver:main',
169 'rc-server=rhodecode.rcserver:main',
179 'rc-setup-app=rhodecode.lib.rc_commands.setup_rc:main',
170 'rc-setup-app=rhodecode.lib.rc_commands.setup_rc:main',
180 'rc-upgrade-db=rhodecode.lib.rc_commands.upgrade_db:main',
171 'rc-upgrade-db=rhodecode.lib.rc_commands.upgrade_db:main',
181 'rc-ishell=rhodecode.lib.rc_commands.ishell:main',
172 'rc-ishell=rhodecode.lib.rc_commands.ishell:main',
182 'rc-ssh-wrapper=rhodecode.apps.ssh_support.lib.ssh_wrapper:main',
173 'rc-ssh-wrapper=rhodecode.apps.ssh_support.lib.ssh_wrapper:main',
183 ],
174 ],
184 'beaker.backends': [
175 'beaker.backends': [
185 'memorylru_base=rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerBase',
176 'memorylru_base=rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerBase',
186 'memorylru_debug=rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerDebug'
177 'memorylru_debug=rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerDebug'
187 ]
178 ]
188 },
179 },
189 )
180 )
General Comments 0
You need to be logged in to leave comments. Login now