##// END OF EJS Templates
caches: enable cache TTL=30s for auth-plugins....
marcink -
r2954:5ef9c564 default
parent child Browse files
Show More
@@ -1,131 +1,132 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
21 import os
22 import logging
22 import logging
23 import importlib
23 import importlib
24
24
25 from pkg_resources import iter_entry_points
25 from pkg_resources import iter_entry_points
26 from pyramid.authentication import SessionAuthenticationPolicy
26 from pyramid.authentication import SessionAuthenticationPolicy
27
27
28 from rhodecode.authentication.registry import AuthenticationPluginRegistry
28 from rhodecode.authentication.registry import AuthenticationPluginRegistry
29 from rhodecode.authentication.routes import root_factory
29 from rhodecode.authentication.routes import root_factory
30 from rhodecode.authentication.routes import AuthnRootResource
30 from rhodecode.authentication.routes import AuthnRootResource
31 from rhodecode.apps._base import ADMIN_PREFIX
31 from rhodecode.apps._base import ADMIN_PREFIX
32 from rhodecode.model.settings import SettingsModel
32 from rhodecode.model.settings import SettingsModel
33
33
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37 # Plugin ID prefixes to distinct between normal and legacy plugins.
37 # Plugin ID prefixes to distinct between normal and legacy plugins.
38 plugin_prefix = 'egg:'
38 plugin_prefix = 'egg:'
39 legacy_plugin_prefix = 'py:'
39 legacy_plugin_prefix = 'py:'
40 plugin_default_auth_ttl = 30
40
41
41
42
42 # TODO: Currently this is only used to discover the authentication plugins.
43 # TODO: Currently this is only used to discover the authentication plugins.
43 # Later on this may be used in a generic way to look up and include all kinds
44 # Later on this may be used in a generic way to look up and include all kinds
44 # of supported enterprise plugins. Therefore this has to be moved and
45 # of supported enterprise plugins. Therefore this has to be moved and
45 # refactored to a real 'plugin look up' machinery.
46 # refactored to a real 'plugin look up' machinery.
46 # TODO: When refactoring this think about splitting it up into distinct
47 # TODO: When refactoring this think about splitting it up into distinct
47 # discover, load and include phases.
48 # discover, load and include phases.
48 def _discover_plugins(config, entry_point='enterprise.plugins1'):
49 def _discover_plugins(config, entry_point='enterprise.plugins1'):
49 for ep in iter_entry_points(entry_point):
50 for ep in iter_entry_points(entry_point):
50 plugin_id = '{}{}#{}'.format(
51 plugin_id = '{}{}#{}'.format(
51 plugin_prefix, ep.dist.project_name, ep.name)
52 plugin_prefix, ep.dist.project_name, ep.name)
52 log.debug('Plugin discovered: "%s"', plugin_id)
53 log.debug('Plugin discovered: "%s"', plugin_id)
53 try:
54 try:
54 module = ep.load()
55 module = ep.load()
55 plugin = module(plugin_id=plugin_id)
56 plugin = module(plugin_id=plugin_id)
56 config.include(plugin.includeme)
57 config.include(plugin.includeme)
57 except Exception as e:
58 except Exception as e:
58 log.exception(
59 log.exception(
59 'Exception while loading authentication plugin '
60 'Exception while loading authentication plugin '
60 '"{}": {}'.format(plugin_id, e.message))
61 '"{}": {}'.format(plugin_id, e.message))
61
62
62
63
63 def _import_legacy_plugin(plugin_id):
64 def _import_legacy_plugin(plugin_id):
64 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
65 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
65 module = importlib.import_module(module_name)
66 module = importlib.import_module(module_name)
66 return module.plugin_factory(plugin_id=plugin_id)
67 return module.plugin_factory(plugin_id=plugin_id)
67
68
68
69
69 def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
70 def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
70 """
71 """
71 Function that imports the legacy plugins stored in the 'auth_plugins'
72 Function that imports the legacy plugins stored in the 'auth_plugins'
72 setting in database which are using the specified prefix. Normally 'py:' is
73 setting in database which are using the specified prefix. Normally 'py:' is
73 used for the legacy plugins.
74 used for the legacy plugins.
74 """
75 """
75 try:
76 try:
76 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
77 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
77 enabled_plugins = auth_plugins.app_settings_value
78 enabled_plugins = auth_plugins.app_settings_value
78 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
79 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
79 except Exception:
80 except Exception:
80 legacy_plugins = []
81 legacy_plugins = []
81
82
82 for plugin_id in legacy_plugins:
83 for plugin_id in legacy_plugins:
83 log.debug('Legacy plugin discovered: "%s"', plugin_id)
84 log.debug('Legacy plugin discovered: "%s"', plugin_id)
84 try:
85 try:
85 plugin = _import_legacy_plugin(plugin_id)
86 plugin = _import_legacy_plugin(plugin_id)
86 config.include(plugin.includeme)
87 config.include(plugin.includeme)
87 except Exception as e:
88 except Exception as e:
88 log.exception(
89 log.exception(
89 'Exception while loading legacy authentication plugin '
90 'Exception while loading legacy authentication plugin '
90 '"{}": {}'.format(plugin_id, e.message))
91 '"{}": {}'.format(plugin_id, e.message))
91
92
92
93
93 def includeme(config):
94 def includeme(config):
94 # Set authentication policy.
95 # Set authentication policy.
95 authn_policy = SessionAuthenticationPolicy()
96 authn_policy = SessionAuthenticationPolicy()
96 config.set_authentication_policy(authn_policy)
97 config.set_authentication_policy(authn_policy)
97
98
98 # Create authentication plugin registry and add it to the pyramid registry.
99 # Create authentication plugin registry and add it to the pyramid registry.
99 authn_registry = AuthenticationPluginRegistry(config.get_settings())
100 authn_registry = AuthenticationPluginRegistry(config.get_settings())
100 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
101 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
101 config.registry.registerUtility(authn_registry)
102 config.registry.registerUtility(authn_registry)
102
103
103 # Create authentication traversal root resource.
104 # Create authentication traversal root resource.
104 authn_root_resource = root_factory()
105 authn_root_resource = root_factory()
105 config.add_directive('add_authn_resource',
106 config.add_directive('add_authn_resource',
106 authn_root_resource.add_authn_resource)
107 authn_root_resource.add_authn_resource)
107
108
108 # Add the authentication traversal route.
109 # Add the authentication traversal route.
109 config.add_route('auth_home',
110 config.add_route('auth_home',
110 ADMIN_PREFIX + '/auth*traverse',
111 ADMIN_PREFIX + '/auth*traverse',
111 factory=root_factory)
112 factory=root_factory)
112 # Add the authentication settings root views.
113 # Add the authentication settings root views.
113 config.add_view('rhodecode.authentication.views.AuthSettingsView',
114 config.add_view('rhodecode.authentication.views.AuthSettingsView',
114 attr='index',
115 attr='index',
115 request_method='GET',
116 request_method='GET',
116 route_name='auth_home',
117 route_name='auth_home',
117 context=AuthnRootResource)
118 context=AuthnRootResource)
118 config.add_view('rhodecode.authentication.views.AuthSettingsView',
119 config.add_view('rhodecode.authentication.views.AuthSettingsView',
119 attr='auth_settings',
120 attr='auth_settings',
120 request_method='POST',
121 request_method='POST',
121 route_name='auth_home',
122 route_name='auth_home',
122 context=AuthnRootResource)
123 context=AuthnRootResource)
123
124
124 for key in ['RC_CMD_SETUP_RC', 'RC_CMD_UPGRADE_DB', 'RC_CMD_SSH_WRAPPER']:
125 for key in ['RC_CMD_SETUP_RC', 'RC_CMD_UPGRADE_DB', 'RC_CMD_SSH_WRAPPER']:
125 if os.environ.get(key):
126 if os.environ.get(key):
126 # skip this heavy step below on certain CLI commands
127 # skip this heavy step below on certain CLI commands
127 return
128 return
128
129
129 # Auto discover authentication plugins and include their configuration.
130 # Auto discover authentication plugins and include their configuration.
130 _discover_plugins(config)
131 _discover_plugins(config)
131 _discover_legacy_plugins(config)
132 _discover_legacy_plugins(config)
@@ -1,759 +1,763 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 Authentication modules
22 Authentication modules
23 """
23 """
24 import socket
24 import socket
25 import string
25 import string
26 import colander
26 import colander
27 import copy
27 import copy
28 import logging
28 import logging
29 import time
29 import time
30 import traceback
30 import traceback
31 import warnings
31 import warnings
32 import functools
32 import functools
33
33
34 from pyramid.threadlocal import get_current_registry
34 from pyramid.threadlocal import get_current_registry
35
35
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.lib import rc_cache
38 from rhodecode.lib import rc_cache
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
40 from rhodecode.lib.utils2 import safe_int, safe_str
40 from rhodecode.lib.utils2 import safe_int, safe_str
41 from rhodecode.lib.exceptions import LdapConnectionError
41 from rhodecode.lib.exceptions import LdapConnectionError
42 from rhodecode.model.db import User
42 from rhodecode.model.db import User
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.user import UserModel
45 from rhodecode.model.user import UserModel
46 from rhodecode.model.user_group import UserGroupModel
46 from rhodecode.model.user_group import UserGroupModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 # auth types that authenticate() function can receive
51 # auth types that authenticate() function can receive
52 VCS_TYPE = 'vcs'
52 VCS_TYPE = 'vcs'
53 HTTP_TYPE = 'http'
53 HTTP_TYPE = 'http'
54
54
55
55
56 class hybrid_property(object):
56 class hybrid_property(object):
57 """
57 """
58 a property decorator that works both for instance and class
58 a property decorator that works both for instance and class
59 """
59 """
60 def __init__(self, fget, fset=None, fdel=None, expr=None):
60 def __init__(self, fget, fset=None, fdel=None, expr=None):
61 self.fget = fget
61 self.fget = fget
62 self.fset = fset
62 self.fset = fset
63 self.fdel = fdel
63 self.fdel = fdel
64 self.expr = expr or fget
64 self.expr = expr or fget
65 functools.update_wrapper(self, fget)
65 functools.update_wrapper(self, fget)
66
66
67 def __get__(self, instance, owner):
67 def __get__(self, instance, owner):
68 if instance is None:
68 if instance is None:
69 return self.expr(owner)
69 return self.expr(owner)
70 else:
70 else:
71 return self.fget(instance)
71 return self.fget(instance)
72
72
73 def __set__(self, instance, value):
73 def __set__(self, instance, value):
74 self.fset(instance, value)
74 self.fset(instance, value)
75
75
76 def __delete__(self, instance):
76 def __delete__(self, instance):
77 self.fdel(instance)
77 self.fdel(instance)
78
78
79
79
80 class LazyFormencode(object):
80 class LazyFormencode(object):
81 def __init__(self, formencode_obj, *args, **kwargs):
81 def __init__(self, formencode_obj, *args, **kwargs):
82 self.formencode_obj = formencode_obj
82 self.formencode_obj = formencode_obj
83 self.args = args
83 self.args = args
84 self.kwargs = kwargs
84 self.kwargs = kwargs
85
85
86 def __call__(self, *args, **kwargs):
86 def __call__(self, *args, **kwargs):
87 from inspect import isfunction
87 from inspect import isfunction
88 formencode_obj = self.formencode_obj
88 formencode_obj = self.formencode_obj
89 if isfunction(formencode_obj):
89 if isfunction(formencode_obj):
90 # case we wrap validators into functions
90 # case we wrap validators into functions
91 formencode_obj = self.formencode_obj(*args, **kwargs)
91 formencode_obj = self.formencode_obj(*args, **kwargs)
92 return formencode_obj(*self.args, **self.kwargs)
92 return formencode_obj(*self.args, **self.kwargs)
93
93
94
94
95 class RhodeCodeAuthPluginBase(object):
95 class RhodeCodeAuthPluginBase(object):
96 # cache the authentication request for N amount of seconds. Some kind
96 # cache the authentication request for N amount of seconds. Some kind
97 # of authentication methods are very heavy and it's very efficient to cache
97 # of authentication methods are very heavy and it's very efficient to cache
98 # the result of a call. If it's set to None (default) cache is off
98 # the result of a call. If it's set to None (default) cache is off
99 AUTH_CACHE_TTL = None
99 AUTH_CACHE_TTL = None
100 AUTH_CACHE = {}
100 AUTH_CACHE = {}
101
101
102 auth_func_attrs = {
102 auth_func_attrs = {
103 "username": "unique username",
103 "username": "unique username",
104 "firstname": "first name",
104 "firstname": "first name",
105 "lastname": "last name",
105 "lastname": "last name",
106 "email": "email address",
106 "email": "email address",
107 "groups": '["list", "of", "groups"]',
107 "groups": '["list", "of", "groups"]',
108 "user_group_sync":
108 "user_group_sync":
109 'True|False defines if returned user groups should be synced',
109 'True|False defines if returned user groups should be synced',
110 "extern_name": "name in external source of record",
110 "extern_name": "name in external source of record",
111 "extern_type": "type of external source of record",
111 "extern_type": "type of external source of record",
112 "admin": 'True|False defines if user should be RhodeCode super admin',
112 "admin": 'True|False defines if user should be RhodeCode super admin',
113 "active":
113 "active":
114 'True|False defines active state of user internally for RhodeCode',
114 'True|False defines active state of user internally for RhodeCode',
115 "active_from_extern":
115 "active_from_extern":
116 "True|False\None, active state from the external auth, "
116 "True|False\None, active state from the external auth, "
117 "None means use definition from RhodeCode extern_type active value"
117 "None means use definition from RhodeCode extern_type active value"
118
118
119 }
119 }
120 # set on authenticate() method and via set_auth_type func.
120 # set on authenticate() method and via set_auth_type func.
121 auth_type = None
121 auth_type = None
122
122
123 # set on authenticate() method and via set_calling_scope_repo, this is a
123 # set on authenticate() method and via set_calling_scope_repo, this is a
124 # calling scope repository when doing authentication most likely on VCS
124 # calling scope repository when doing authentication most likely on VCS
125 # operations
125 # operations
126 acl_repo_name = None
126 acl_repo_name = None
127
127
128 # List of setting names to store encrypted. Plugins may override this list
128 # List of setting names to store encrypted. Plugins may override this list
129 # to store settings encrypted.
129 # to store settings encrypted.
130 _settings_encrypted = []
130 _settings_encrypted = []
131
131
132 # Mapping of python to DB settings model types. Plugins may override or
132 # Mapping of python to DB settings model types. Plugins may override or
133 # extend this mapping.
133 # extend this mapping.
134 _settings_type_map = {
134 _settings_type_map = {
135 colander.String: 'unicode',
135 colander.String: 'unicode',
136 colander.Integer: 'int',
136 colander.Integer: 'int',
137 colander.Boolean: 'bool',
137 colander.Boolean: 'bool',
138 colander.List: 'list',
138 colander.List: 'list',
139 }
139 }
140
140
141 # list of keys in settings that are unsafe to be logged, should be passwords
141 # list of keys in settings that are unsafe to be logged, should be passwords
142 # or other crucial credentials
142 # or other crucial credentials
143 _settings_unsafe_keys = []
143 _settings_unsafe_keys = []
144
144
145 def __init__(self, plugin_id):
145 def __init__(self, plugin_id):
146 self._plugin_id = plugin_id
146 self._plugin_id = plugin_id
147
147
148 def __str__(self):
148 def __str__(self):
149 return self.get_id()
149 return self.get_id()
150
150
151 def _get_setting_full_name(self, name):
151 def _get_setting_full_name(self, name):
152 """
152 """
153 Return the full setting name used for storing values in the database.
153 Return the full setting name used for storing values in the database.
154 """
154 """
155 # TODO: johbo: Using the name here is problematic. It would be good to
155 # TODO: johbo: Using the name here is problematic. It would be good to
156 # introduce either new models in the database to hold Plugin and
156 # introduce either new models in the database to hold Plugin and
157 # PluginSetting or to use the plugin id here.
157 # PluginSetting or to use the plugin id here.
158 return 'auth_{}_{}'.format(self.name, name)
158 return 'auth_{}_{}'.format(self.name, name)
159
159
160 def _get_setting_type(self, name):
160 def _get_setting_type(self, name):
161 """
161 """
162 Return the type of a setting. This type is defined by the SettingsModel
162 Return the type of a setting. This type is defined by the SettingsModel
163 and determines how the setting is stored in DB. Optionally the suffix
163 and determines how the setting is stored in DB. Optionally the suffix
164 `.encrypted` is appended to instruct SettingsModel to store it
164 `.encrypted` is appended to instruct SettingsModel to store it
165 encrypted.
165 encrypted.
166 """
166 """
167 schema_node = self.get_settings_schema().get(name)
167 schema_node = self.get_settings_schema().get(name)
168 db_type = self._settings_type_map.get(
168 db_type = self._settings_type_map.get(
169 type(schema_node.typ), 'unicode')
169 type(schema_node.typ), 'unicode')
170 if name in self._settings_encrypted:
170 if name in self._settings_encrypted:
171 db_type = '{}.encrypted'.format(db_type)
171 db_type = '{}.encrypted'.format(db_type)
172 return db_type
172 return db_type
173
173
174 def is_enabled(self):
174 def is_enabled(self):
175 """
175 """
176 Returns true if this plugin is enabled. An enabled plugin can be
176 Returns true if this plugin is enabled. An enabled plugin can be
177 configured in the admin interface but it is not consulted during
177 configured in the admin interface but it is not consulted during
178 authentication.
178 authentication.
179 """
179 """
180 auth_plugins = SettingsModel().get_auth_plugins()
180 auth_plugins = SettingsModel().get_auth_plugins()
181 return self.get_id() in auth_plugins
181 return self.get_id() in auth_plugins
182
182
183 def is_active(self, plugin_cached_settings=None):
183 def is_active(self, plugin_cached_settings=None):
184 """
184 """
185 Returns true if the plugin is activated. An activated plugin is
185 Returns true if the plugin is activated. An activated plugin is
186 consulted during authentication, assumed it is also enabled.
186 consulted during authentication, assumed it is also enabled.
187 """
187 """
188 return self.get_setting_by_name(
188 return self.get_setting_by_name(
189 'enabled', plugin_cached_settings=plugin_cached_settings)
189 'enabled', plugin_cached_settings=plugin_cached_settings)
190
190
191 def get_id(self):
191 def get_id(self):
192 """
192 """
193 Returns the plugin id.
193 Returns the plugin id.
194 """
194 """
195 return self._plugin_id
195 return self._plugin_id
196
196
197 def get_display_name(self):
197 def get_display_name(self):
198 """
198 """
199 Returns a translation string for displaying purposes.
199 Returns a translation string for displaying purposes.
200 """
200 """
201 raise NotImplementedError('Not implemented in base class')
201 raise NotImplementedError('Not implemented in base class')
202
202
203 def get_settings_schema(self):
203 def get_settings_schema(self):
204 """
204 """
205 Returns a colander schema, representing the plugin settings.
205 Returns a colander schema, representing the plugin settings.
206 """
206 """
207 return AuthnPluginSettingsSchemaBase()
207 return AuthnPluginSettingsSchemaBase()
208
208
209 def get_settings(self):
209 def get_settings(self):
210 """
210 """
211 Returns the plugin settings as dictionary.
211 Returns the plugin settings as dictionary.
212 """
212 """
213 settings = {}
213 settings = {}
214 raw_settings = SettingsModel().get_all_settings()
214 raw_settings = SettingsModel().get_all_settings()
215 for node in self.get_settings_schema():
215 for node in self.get_settings_schema():
216 settings[node.name] = self.get_setting_by_name(
216 settings[node.name] = self.get_setting_by_name(
217 node.name, plugin_cached_settings=raw_settings)
217 node.name, plugin_cached_settings=raw_settings)
218 return settings
218 return settings
219
219
220 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
220 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
221 """
221 """
222 Returns a plugin setting by name.
222 Returns a plugin setting by name.
223 """
223 """
224 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
224 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
225 if plugin_cached_settings:
225 if plugin_cached_settings:
226 plugin_settings = plugin_cached_settings
226 plugin_settings = plugin_cached_settings
227 else:
227 else:
228 plugin_settings = SettingsModel().get_all_settings()
228 plugin_settings = SettingsModel().get_all_settings()
229
229
230 if full_name in plugin_settings:
230 if full_name in plugin_settings:
231 return plugin_settings[full_name]
231 return plugin_settings[full_name]
232 else:
232 else:
233 return default
233 return default
234
234
235 def create_or_update_setting(self, name, value):
235 def create_or_update_setting(self, name, value):
236 """
236 """
237 Create or update a setting for this plugin in the persistent storage.
237 Create or update a setting for this plugin in the persistent storage.
238 """
238 """
239 full_name = self._get_setting_full_name(name)
239 full_name = self._get_setting_full_name(name)
240 type_ = self._get_setting_type(name)
240 type_ = self._get_setting_type(name)
241 db_setting = SettingsModel().create_or_update_setting(
241 db_setting = SettingsModel().create_or_update_setting(
242 full_name, value, type_)
242 full_name, value, type_)
243 return db_setting.app_settings_value
243 return db_setting.app_settings_value
244
244
245 def log_safe_settings(self, settings):
245 def log_safe_settings(self, settings):
246 """
246 """
247 returns a log safe representation of settings, without any secrets
247 returns a log safe representation of settings, without any secrets
248 """
248 """
249 settings_copy = copy.deepcopy(settings)
249 settings_copy = copy.deepcopy(settings)
250 for k in self._settings_unsafe_keys:
250 for k in self._settings_unsafe_keys:
251 if k in settings_copy:
251 if k in settings_copy:
252 del settings_copy[k]
252 del settings_copy[k]
253 return settings_copy
253 return settings_copy
254
254
255 @hybrid_property
255 @hybrid_property
256 def name(self):
256 def name(self):
257 """
257 """
258 Returns the name of this authentication plugin.
258 Returns the name of this authentication plugin.
259
259
260 :returns: string
260 :returns: string
261 """
261 """
262 raise NotImplementedError("Not implemented in base class")
262 raise NotImplementedError("Not implemented in base class")
263
263
264 def get_url_slug(self):
264 def get_url_slug(self):
265 """
265 """
266 Returns a slug which should be used when constructing URLs which refer
266 Returns a slug which should be used when constructing URLs which refer
267 to this plugin. By default it returns the plugin name. If the name is
267 to this plugin. By default it returns the plugin name. If the name is
268 not suitable for using it in an URL the plugin should override this
268 not suitable for using it in an URL the plugin should override this
269 method.
269 method.
270 """
270 """
271 return self.name
271 return self.name
272
272
273 @property
273 @property
274 def is_headers_auth(self):
274 def is_headers_auth(self):
275 """
275 """
276 Returns True if this authentication plugin uses HTTP headers as
276 Returns True if this authentication plugin uses HTTP headers as
277 authentication method.
277 authentication method.
278 """
278 """
279 return False
279 return False
280
280
281 @hybrid_property
281 @hybrid_property
282 def is_container_auth(self):
282 def is_container_auth(self):
283 """
283 """
284 Deprecated method that indicates if this authentication plugin uses
284 Deprecated method that indicates if this authentication plugin uses
285 HTTP headers as authentication method.
285 HTTP headers as authentication method.
286 """
286 """
287 warnings.warn(
287 warnings.warn(
288 'Use is_headers_auth instead.', category=DeprecationWarning)
288 'Use is_headers_auth instead.', category=DeprecationWarning)
289 return self.is_headers_auth
289 return self.is_headers_auth
290
290
291 @hybrid_property
291 @hybrid_property
292 def allows_creating_users(self):
292 def allows_creating_users(self):
293 """
293 """
294 Defines if Plugin allows users to be created on-the-fly when
294 Defines if Plugin allows users to be created on-the-fly when
295 authentication is called. Controls how external plugins should behave
295 authentication is called. Controls how external plugins should behave
296 in terms if they are allowed to create new users, or not. Base plugins
296 in terms if they are allowed to create new users, or not. Base plugins
297 should not be allowed to, but External ones should be !
297 should not be allowed to, but External ones should be !
298
298
299 :return: bool
299 :return: bool
300 """
300 """
301 return False
301 return False
302
302
303 def set_auth_type(self, auth_type):
303 def set_auth_type(self, auth_type):
304 self.auth_type = auth_type
304 self.auth_type = auth_type
305
305
306 def set_calling_scope_repo(self, acl_repo_name):
306 def set_calling_scope_repo(self, acl_repo_name):
307 self.acl_repo_name = acl_repo_name
307 self.acl_repo_name = acl_repo_name
308
308
309 def allows_authentication_from(
309 def allows_authentication_from(
310 self, user, allows_non_existing_user=True,
310 self, user, allows_non_existing_user=True,
311 allowed_auth_plugins=None, allowed_auth_sources=None):
311 allowed_auth_plugins=None, allowed_auth_sources=None):
312 """
312 """
313 Checks if this authentication module should accept a request for
313 Checks if this authentication module should accept a request for
314 the current user.
314 the current user.
315
315
316 :param user: user object fetched using plugin's get_user() method.
316 :param user: user object fetched using plugin's get_user() method.
317 :param allows_non_existing_user: if True, don't allow the
317 :param allows_non_existing_user: if True, don't allow the
318 user to be empty, meaning not existing in our database
318 user to be empty, meaning not existing in our database
319 :param allowed_auth_plugins: if provided, users extern_type will be
319 :param allowed_auth_plugins: if provided, users extern_type will be
320 checked against a list of provided extern types, which are plugin
320 checked against a list of provided extern types, which are plugin
321 auth_names in the end
321 auth_names in the end
322 :param allowed_auth_sources: authentication type allowed,
322 :param allowed_auth_sources: authentication type allowed,
323 `http` or `vcs` default is both.
323 `http` or `vcs` default is both.
324 defines if plugin will accept only http authentication vcs
324 defines if plugin will accept only http authentication vcs
325 authentication(git/hg) or both
325 authentication(git/hg) or both
326 :returns: boolean
326 :returns: boolean
327 """
327 """
328 if not user and not allows_non_existing_user:
328 if not user and not allows_non_existing_user:
329 log.debug('User is empty but plugin does not allow empty users,'
329 log.debug('User is empty but plugin does not allow empty users,'
330 'not allowed to authenticate')
330 'not allowed to authenticate')
331 return False
331 return False
332
332
333 expected_auth_plugins = allowed_auth_plugins or [self.name]
333 expected_auth_plugins = allowed_auth_plugins or [self.name]
334 if user and (user.extern_type and
334 if user and (user.extern_type and
335 user.extern_type not in expected_auth_plugins):
335 user.extern_type not in expected_auth_plugins):
336 log.debug(
336 log.debug(
337 'User `%s` is bound to `%s` auth type. Plugin allows only '
337 'User `%s` is bound to `%s` auth type. Plugin allows only '
338 '%s, skipping', user, user.extern_type, expected_auth_plugins)
338 '%s, skipping', user, user.extern_type, expected_auth_plugins)
339
339
340 return False
340 return False
341
341
342 # by default accept both
342 # by default accept both
343 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
343 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
344 if self.auth_type not in expected_auth_from:
344 if self.auth_type not in expected_auth_from:
345 log.debug('Current auth source is %s but plugin only allows %s',
345 log.debug('Current auth source is %s but plugin only allows %s',
346 self.auth_type, expected_auth_from)
346 self.auth_type, expected_auth_from)
347 return False
347 return False
348
348
349 return True
349 return True
350
350
351 def get_user(self, username=None, **kwargs):
351 def get_user(self, username=None, **kwargs):
352 """
352 """
353 Helper method for user fetching in plugins, by default it's using
353 Helper method for user fetching in plugins, by default it's using
354 simple fetch by username, but this method can be custimized in plugins
354 simple fetch by username, but this method can be custimized in plugins
355 eg. headers auth plugin to fetch user by environ params
355 eg. headers auth plugin to fetch user by environ params
356
356
357 :param username: username if given to fetch from database
357 :param username: username if given to fetch from database
358 :param kwargs: extra arguments needed for user fetching.
358 :param kwargs: extra arguments needed for user fetching.
359 """
359 """
360 user = None
360 user = None
361 log.debug(
361 log.debug(
362 'Trying to fetch user `%s` from RhodeCode database', username)
362 'Trying to fetch user `%s` from RhodeCode database', username)
363 if username:
363 if username:
364 user = User.get_by_username(username)
364 user = User.get_by_username(username)
365 if not user:
365 if not user:
366 log.debug('User not found, fallback to fetch user in '
366 log.debug('User not found, fallback to fetch user in '
367 'case insensitive mode')
367 'case insensitive mode')
368 user = User.get_by_username(username, case_insensitive=True)
368 user = User.get_by_username(username, case_insensitive=True)
369 else:
369 else:
370 log.debug('provided username:`%s` is empty skipping...', username)
370 log.debug('provided username:`%s` is empty skipping...', username)
371 if not user:
371 if not user:
372 log.debug('User `%s` not found in database', username)
372 log.debug('User `%s` not found in database', username)
373 else:
373 else:
374 log.debug('Got DB user:%s', user)
374 log.debug('Got DB user:%s', user)
375 return user
375 return user
376
376
377 def user_activation_state(self):
377 def user_activation_state(self):
378 """
378 """
379 Defines user activation state when creating new users
379 Defines user activation state when creating new users
380
380
381 :returns: boolean
381 :returns: boolean
382 """
382 """
383 raise NotImplementedError("Not implemented in base class")
383 raise NotImplementedError("Not implemented in base class")
384
384
385 def auth(self, userobj, username, passwd, settings, **kwargs):
385 def auth(self, userobj, username, passwd, settings, **kwargs):
386 """
386 """
387 Given a user object (which may be null), username, a plaintext
387 Given a user object (which may be null), username, a plaintext
388 password, and a settings object (containing all the keys needed as
388 password, and a settings object (containing all the keys needed as
389 listed in settings()), authenticate this user's login attempt.
389 listed in settings()), authenticate this user's login attempt.
390
390
391 Return None on failure. On success, return a dictionary of the form:
391 Return None on failure. On success, return a dictionary of the form:
392
392
393 see: RhodeCodeAuthPluginBase.auth_func_attrs
393 see: RhodeCodeAuthPluginBase.auth_func_attrs
394 This is later validated for correctness
394 This is later validated for correctness
395 """
395 """
396 raise NotImplementedError("not implemented in base class")
396 raise NotImplementedError("not implemented in base class")
397
397
398 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
398 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
399 """
399 """
400 Wrapper to call self.auth() that validates call on it
400 Wrapper to call self.auth() that validates call on it
401
401
402 :param userobj: userobj
402 :param userobj: userobj
403 :param username: username
403 :param username: username
404 :param passwd: plaintext password
404 :param passwd: plaintext password
405 :param settings: plugin settings
405 :param settings: plugin settings
406 """
406 """
407 auth = self.auth(userobj, username, passwd, settings, **kwargs)
407 auth = self.auth(userobj, username, passwd, settings, **kwargs)
408 if auth:
408 if auth:
409 auth['_plugin'] = self.name
409 auth['_plugin'] = self.name
410 auth['_ttl_cache'] = self.get_ttl_cache(settings)
410 auth['_ttl_cache'] = self.get_ttl_cache(settings)
411 # check if hash should be migrated ?
411 # check if hash should be migrated ?
412 new_hash = auth.get('_hash_migrate')
412 new_hash = auth.get('_hash_migrate')
413 if new_hash:
413 if new_hash:
414 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
414 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
415 if 'user_group_sync' not in auth:
415 if 'user_group_sync' not in auth:
416 auth['user_group_sync'] = False
416 auth['user_group_sync'] = False
417 return self._validate_auth_return(auth)
417 return self._validate_auth_return(auth)
418 return auth
418 return auth
419
419
420 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
420 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
421 new_hash_cypher = _RhodeCodeCryptoBCrypt()
421 new_hash_cypher = _RhodeCodeCryptoBCrypt()
422 # extra checks, so make sure new hash is correct.
422 # extra checks, so make sure new hash is correct.
423 password_encoded = safe_str(password)
423 password_encoded = safe_str(password)
424 if new_hash and new_hash_cypher.hash_check(
424 if new_hash and new_hash_cypher.hash_check(
425 password_encoded, new_hash):
425 password_encoded, new_hash):
426 cur_user = User.get_by_username(username)
426 cur_user = User.get_by_username(username)
427 cur_user.password = new_hash
427 cur_user.password = new_hash
428 Session().add(cur_user)
428 Session().add(cur_user)
429 Session().flush()
429 Session().flush()
430 log.info('Migrated user %s hash to bcrypt', cur_user)
430 log.info('Migrated user %s hash to bcrypt', cur_user)
431
431
432 def _validate_auth_return(self, ret):
432 def _validate_auth_return(self, ret):
433 if not isinstance(ret, dict):
433 if not isinstance(ret, dict):
434 raise Exception('returned value from auth must be a dict')
434 raise Exception('returned value from auth must be a dict')
435 for k in self.auth_func_attrs:
435 for k in self.auth_func_attrs:
436 if k not in ret:
436 if k not in ret:
437 raise Exception('Missing %s attribute from returned data' % k)
437 raise Exception('Missing %s attribute from returned data' % k)
438 return ret
438 return ret
439
439
440 def get_ttl_cache(self, settings=None):
440 def get_ttl_cache(self, settings=None):
441 plugin_settings = settings or self.get_settings()
441 plugin_settings = settings or self.get_settings()
442 cache_ttl = 0
442 # we set default to 30, we make a compromise here,
443 # performance > security, mostly due to LDAP/SVN, majority
444 # of users pick cache_ttl to be enabled
445 from rhodecode.authentication import plugin_default_auth_ttl
446 cache_ttl = plugin_default_auth_ttl
443
447
444 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
448 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
445 # plugin cache set inside is more important than the settings value
449 # plugin cache set inside is more important than the settings value
446 cache_ttl = self.AUTH_CACHE_TTL
450 cache_ttl = self.AUTH_CACHE_TTL
447 elif plugin_settings.get('cache_ttl'):
451 elif plugin_settings.get('cache_ttl'):
448 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
452 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
449
453
450 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
454 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
451 return plugin_cache_active, cache_ttl
455 return plugin_cache_active, cache_ttl
452
456
453
457
454 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
458 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
455
459
456 @hybrid_property
460 @hybrid_property
457 def allows_creating_users(self):
461 def allows_creating_users(self):
458 return True
462 return True
459
463
460 def use_fake_password(self):
464 def use_fake_password(self):
461 """
465 """
462 Return a boolean that indicates whether or not we should set the user's
466 Return a boolean that indicates whether or not we should set the user's
463 password to a random value when it is authenticated by this plugin.
467 password to a random value when it is authenticated by this plugin.
464 If your plugin provides authentication, then you will generally
468 If your plugin provides authentication, then you will generally
465 want this.
469 want this.
466
470
467 :returns: boolean
471 :returns: boolean
468 """
472 """
469 raise NotImplementedError("Not implemented in base class")
473 raise NotImplementedError("Not implemented in base class")
470
474
471 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
475 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
472 # at this point _authenticate calls plugin's `auth()` function
476 # at this point _authenticate calls plugin's `auth()` function
473 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
477 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
474 userobj, username, passwd, settings, **kwargs)
478 userobj, username, passwd, settings, **kwargs)
475
479
476 if auth:
480 if auth:
477 # maybe plugin will clean the username ?
481 # maybe plugin will clean the username ?
478 # we should use the return value
482 # we should use the return value
479 username = auth['username']
483 username = auth['username']
480
484
481 # if external source tells us that user is not active, we should
485 # if external source tells us that user is not active, we should
482 # skip rest of the process. This can prevent from creating users in
486 # skip rest of the process. This can prevent from creating users in
483 # RhodeCode when using external authentication, but if it's
487 # RhodeCode when using external authentication, but if it's
484 # inactive user we shouldn't create that user anyway
488 # inactive user we shouldn't create that user anyway
485 if auth['active_from_extern'] is False:
489 if auth['active_from_extern'] is False:
486 log.warning(
490 log.warning(
487 "User %s authenticated against %s, but is inactive",
491 "User %s authenticated against %s, but is inactive",
488 username, self.__module__)
492 username, self.__module__)
489 return None
493 return None
490
494
491 cur_user = User.get_by_username(username, case_insensitive=True)
495 cur_user = User.get_by_username(username, case_insensitive=True)
492 is_user_existing = cur_user is not None
496 is_user_existing = cur_user is not None
493
497
494 if is_user_existing:
498 if is_user_existing:
495 log.debug('Syncing user `%s` from '
499 log.debug('Syncing user `%s` from '
496 '`%s` plugin', username, self.name)
500 '`%s` plugin', username, self.name)
497 else:
501 else:
498 log.debug('Creating non existing user `%s` from '
502 log.debug('Creating non existing user `%s` from '
499 '`%s` plugin', username, self.name)
503 '`%s` plugin', username, self.name)
500
504
501 if self.allows_creating_users:
505 if self.allows_creating_users:
502 log.debug('Plugin `%s` allows to '
506 log.debug('Plugin `%s` allows to '
503 'create new users', self.name)
507 'create new users', self.name)
504 else:
508 else:
505 log.debug('Plugin `%s` does not allow to '
509 log.debug('Plugin `%s` does not allow to '
506 'create new users', self.name)
510 'create new users', self.name)
507
511
508 user_parameters = {
512 user_parameters = {
509 'username': username,
513 'username': username,
510 'email': auth["email"],
514 'email': auth["email"],
511 'firstname': auth["firstname"],
515 'firstname': auth["firstname"],
512 'lastname': auth["lastname"],
516 'lastname': auth["lastname"],
513 'active': auth["active"],
517 'active': auth["active"],
514 'admin': auth["admin"],
518 'admin': auth["admin"],
515 'extern_name': auth["extern_name"],
519 'extern_name': auth["extern_name"],
516 'extern_type': self.name,
520 'extern_type': self.name,
517 'plugin': self,
521 'plugin': self,
518 'allow_to_create_user': self.allows_creating_users,
522 'allow_to_create_user': self.allows_creating_users,
519 }
523 }
520
524
521 if not is_user_existing:
525 if not is_user_existing:
522 if self.use_fake_password():
526 if self.use_fake_password():
523 # Randomize the PW because we don't need it, but don't want
527 # Randomize the PW because we don't need it, but don't want
524 # them blank either
528 # them blank either
525 passwd = PasswordGenerator().gen_password(length=16)
529 passwd = PasswordGenerator().gen_password(length=16)
526 user_parameters['password'] = passwd
530 user_parameters['password'] = passwd
527 else:
531 else:
528 # Since the password is required by create_or_update method of
532 # Since the password is required by create_or_update method of
529 # UserModel, we need to set it explicitly.
533 # UserModel, we need to set it explicitly.
530 # The create_or_update method is smart and recognises the
534 # The create_or_update method is smart and recognises the
531 # password hashes as well.
535 # password hashes as well.
532 user_parameters['password'] = cur_user.password
536 user_parameters['password'] = cur_user.password
533
537
534 # we either create or update users, we also pass the flag
538 # we either create or update users, we also pass the flag
535 # that controls if this method can actually do that.
539 # that controls if this method can actually do that.
536 # raises NotAllowedToCreateUserError if it cannot, and we try to.
540 # raises NotAllowedToCreateUserError if it cannot, and we try to.
537 user = UserModel().create_or_update(**user_parameters)
541 user = UserModel().create_or_update(**user_parameters)
538 Session().flush()
542 Session().flush()
539 # enforce user is just in given groups, all of them has to be ones
543 # enforce user is just in given groups, all of them has to be ones
540 # created from plugins. We store this info in _group_data JSON
544 # created from plugins. We store this info in _group_data JSON
541 # field
545 # field
542
546
543 if auth['user_group_sync']:
547 if auth['user_group_sync']:
544 try:
548 try:
545 groups = auth['groups'] or []
549 groups = auth['groups'] or []
546 log.debug(
550 log.debug(
547 'Performing user_group sync based on set `%s` '
551 'Performing user_group sync based on set `%s` '
548 'returned by `%s` plugin', groups, self.name)
552 'returned by `%s` plugin', groups, self.name)
549 UserGroupModel().enforce_groups(user, groups, self.name)
553 UserGroupModel().enforce_groups(user, groups, self.name)
550 except Exception:
554 except Exception:
551 # for any reason group syncing fails, we should
555 # for any reason group syncing fails, we should
552 # proceed with login
556 # proceed with login
553 log.error(traceback.format_exc())
557 log.error(traceback.format_exc())
554
558
555 Session().commit()
559 Session().commit()
556 return auth
560 return auth
557
561
558
562
559 class AuthLdapBase(object):
563 class AuthLdapBase(object):
560
564
561 @classmethod
565 @classmethod
562 def _build_servers(cls, ldap_server_type, ldap_server, port):
566 def _build_servers(cls, ldap_server_type, ldap_server, port):
563 def host_resolver(host, port, full_resolve=True):
567 def host_resolver(host, port, full_resolve=True):
564 """
568 """
565 Main work for this function is to prevent ldap connection issues,
569 Main work for this function is to prevent ldap connection issues,
566 and detect them early using a "greenified" sockets
570 and detect them early using a "greenified" sockets
567 """
571 """
568 host = host.strip()
572 host = host.strip()
569 if not full_resolve:
573 if not full_resolve:
570 return '{}:{}'.format(host, port)
574 return '{}:{}'.format(host, port)
571
575
572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
576 log.debug('LDAP: Resolving IP for LDAP host %s', host)
573 try:
577 try:
574 ip = socket.gethostbyname(host)
578 ip = socket.gethostbyname(host)
575 log.debug('Got LDAP server %s ip %s', host, ip)
579 log.debug('Got LDAP server %s ip %s', host, ip)
576 except Exception:
580 except Exception:
577 raise LdapConnectionError(
581 raise LdapConnectionError(
578 'Failed to resolve host: `{}`'.format(host))
582 'Failed to resolve host: `{}`'.format(host))
579
583
580 log.debug('LDAP: Checking if IP %s is accessible', ip)
584 log.debug('LDAP: Checking if IP %s is accessible', ip)
581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
585 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
582 try:
586 try:
583 s.connect((ip, int(port)))
587 s.connect((ip, int(port)))
584 s.shutdown(socket.SHUT_RD)
588 s.shutdown(socket.SHUT_RD)
585 except Exception:
589 except Exception:
586 raise LdapConnectionError(
590 raise LdapConnectionError(
587 'Failed to connect to host: `{}:{}`'.format(host, port))
591 'Failed to connect to host: `{}:{}`'.format(host, port))
588
592
589 return '{}:{}'.format(host, port)
593 return '{}:{}'.format(host, port)
590
594
591 if len(ldap_server) == 1:
595 if len(ldap_server) == 1:
592 # in case of single server use resolver to detect potential
596 # in case of single server use resolver to detect potential
593 # connection issues
597 # connection issues
594 full_resolve = True
598 full_resolve = True
595 else:
599 else:
596 full_resolve = False
600 full_resolve = False
597
601
598 return ', '.join(
602 return ', '.join(
599 ["{}://{}".format(
603 ["{}://{}".format(
600 ldap_server_type,
604 ldap_server_type,
601 host_resolver(host, port, full_resolve=full_resolve))
605 host_resolver(host, port, full_resolve=full_resolve))
602 for host in ldap_server])
606 for host in ldap_server])
603
607
604 @classmethod
608 @classmethod
605 def _get_server_list(cls, servers):
609 def _get_server_list(cls, servers):
606 return map(string.strip, servers.split(','))
610 return map(string.strip, servers.split(','))
607
611
608 @classmethod
612 @classmethod
609 def get_uid(cls, username, server_addresses):
613 def get_uid(cls, username, server_addresses):
610 uid = username
614 uid = username
611 for server_addr in server_addresses:
615 for server_addr in server_addresses:
612 uid = chop_at(username, "@%s" % server_addr)
616 uid = chop_at(username, "@%s" % server_addr)
613 return uid
617 return uid
614
618
615
619
616 def loadplugin(plugin_id):
620 def loadplugin(plugin_id):
617 """
621 """
618 Loads and returns an instantiated authentication plugin.
622 Loads and returns an instantiated authentication plugin.
619 Returns the RhodeCodeAuthPluginBase subclass on success,
623 Returns the RhodeCodeAuthPluginBase subclass on success,
620 or None on failure.
624 or None on failure.
621 """
625 """
622 # TODO: Disusing pyramids thread locals to retrieve the registry.
626 # TODO: Disusing pyramids thread locals to retrieve the registry.
623 authn_registry = get_authn_registry()
627 authn_registry = get_authn_registry()
624 plugin = authn_registry.get_plugin(plugin_id)
628 plugin = authn_registry.get_plugin(plugin_id)
625 if plugin is None:
629 if plugin is None:
626 log.error('Authentication plugin not found: "%s"', plugin_id)
630 log.error('Authentication plugin not found: "%s"', plugin_id)
627 return plugin
631 return plugin
628
632
629
633
630 def get_authn_registry(registry=None):
634 def get_authn_registry(registry=None):
631 registry = registry or get_current_registry()
635 registry = registry or get_current_registry()
632 authn_registry = registry.getUtility(IAuthnPluginRegistry)
636 authn_registry = registry.getUtility(IAuthnPluginRegistry)
633 return authn_registry
637 return authn_registry
634
638
635
639
636 def authenticate(username, password, environ=None, auth_type=None,
640 def authenticate(username, password, environ=None, auth_type=None,
637 skip_missing=False, registry=None, acl_repo_name=None):
641 skip_missing=False, registry=None, acl_repo_name=None):
638 """
642 """
639 Authentication function used for access control,
643 Authentication function used for access control,
640 It tries to authenticate based on enabled authentication modules.
644 It tries to authenticate based on enabled authentication modules.
641
645
642 :param username: username can be empty for headers auth
646 :param username: username can be empty for headers auth
643 :param password: password can be empty for headers auth
647 :param password: password can be empty for headers auth
644 :param environ: environ headers passed for headers auth
648 :param environ: environ headers passed for headers auth
645 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
649 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
646 :param skip_missing: ignores plugins that are in db but not in environment
650 :param skip_missing: ignores plugins that are in db but not in environment
647 :returns: None if auth failed, plugin_user dict if auth is correct
651 :returns: None if auth failed, plugin_user dict if auth is correct
648 """
652 """
649 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
653 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
650 raise ValueError('auth type must be on of http, vcs got "%s" instead'
654 raise ValueError('auth type must be on of http, vcs got "%s" instead'
651 % auth_type)
655 % auth_type)
652 headers_only = environ and not (username and password)
656 headers_only = environ and not (username and password)
653
657
654 authn_registry = get_authn_registry(registry)
658 authn_registry = get_authn_registry(registry)
655 plugins_to_check = authn_registry.get_plugins_for_authentication()
659 plugins_to_check = authn_registry.get_plugins_for_authentication()
656 log.debug('Starting ordered authentication chain using %s plugins',
660 log.debug('Starting ordered authentication chain using %s plugins',
657 [x.name for x in plugins_to_check])
661 [x.name for x in plugins_to_check])
658 for plugin in plugins_to_check:
662 for plugin in plugins_to_check:
659 plugin.set_auth_type(auth_type)
663 plugin.set_auth_type(auth_type)
660 plugin.set_calling_scope_repo(acl_repo_name)
664 plugin.set_calling_scope_repo(acl_repo_name)
661
665
662 if headers_only and not plugin.is_headers_auth:
666 if headers_only and not plugin.is_headers_auth:
663 log.debug('Auth type is for headers only and plugin `%s` is not '
667 log.debug('Auth type is for headers only and plugin `%s` is not '
664 'headers plugin, skipping...', plugin.get_id())
668 'headers plugin, skipping...', plugin.get_id())
665 continue
669 continue
666
670
667 log.debug('Trying authentication using ** %s **', plugin.get_id())
671 log.debug('Trying authentication using ** %s **', plugin.get_id())
668
672
669 # load plugin settings from RhodeCode database
673 # load plugin settings from RhodeCode database
670 plugin_settings = plugin.get_settings()
674 plugin_settings = plugin.get_settings()
671 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
675 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
672 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
676 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
673
677
674 # use plugin's method of user extraction.
678 # use plugin's method of user extraction.
675 user = plugin.get_user(username, environ=environ,
679 user = plugin.get_user(username, environ=environ,
676 settings=plugin_settings)
680 settings=plugin_settings)
677 display_user = user.username if user else username
681 display_user = user.username if user else username
678 log.debug(
682 log.debug(
679 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
683 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
680
684
681 if not plugin.allows_authentication_from(user):
685 if not plugin.allows_authentication_from(user):
682 log.debug('Plugin %s does not accept user `%s` for authentication',
686 log.debug('Plugin %s does not accept user `%s` for authentication',
683 plugin.get_id(), display_user)
687 plugin.get_id(), display_user)
684 continue
688 continue
685 else:
689 else:
686 log.debug('Plugin %s accepted user `%s` for authentication',
690 log.debug('Plugin %s accepted user `%s` for authentication',
687 plugin.get_id(), display_user)
691 plugin.get_id(), display_user)
688
692
689 log.info('Authenticating user `%s` using %s plugin',
693 log.info('Authenticating user `%s` using %s plugin',
690 display_user, plugin.get_id())
694 display_user, plugin.get_id())
691
695
692 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
696 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
693
697
694 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
698 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
695 plugin.get_id(), plugin_cache_active, cache_ttl)
699 plugin.get_id(), plugin_cache_active, cache_ttl)
696
700
697 user_id = user.user_id if user else None
701 user_id = user.user_id if user else None
698 # don't cache for empty users
702 # don't cache for empty users
699 plugin_cache_active = plugin_cache_active and user_id
703 plugin_cache_active = plugin_cache_active and user_id
700 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
704 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
701 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
705 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
702
706
703 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
707 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
704 expiration_time=cache_ttl,
708 expiration_time=cache_ttl,
705 condition=plugin_cache_active)
709 condition=plugin_cache_active)
706 def compute_auth(
710 def compute_auth(
707 cache_name, plugin_name, username, password):
711 cache_name, plugin_name, username, password):
708
712
709 # _authenticate is a wrapper for .auth() method of plugin.
713 # _authenticate is a wrapper for .auth() method of plugin.
710 # it checks if .auth() sends proper data.
714 # it checks if .auth() sends proper data.
711 # For RhodeCodeExternalAuthPlugin it also maps users to
715 # For RhodeCodeExternalAuthPlugin it also maps users to
712 # Database and maps the attributes returned from .auth()
716 # Database and maps the attributes returned from .auth()
713 # to RhodeCode database. If this function returns data
717 # to RhodeCode database. If this function returns data
714 # then auth is correct.
718 # then auth is correct.
715 log.debug('Running plugin `%s` _authenticate method '
719 log.debug('Running plugin `%s` _authenticate method '
716 'using username and password', plugin.get_id())
720 'using username and password', plugin.get_id())
717 return plugin._authenticate(
721 return plugin._authenticate(
718 user, username, password, plugin_settings,
722 user, username, password, plugin_settings,
719 environ=environ or {})
723 environ=environ or {})
720
724
721 start = time.time()
725 start = time.time()
722 # for environ based auth, password can be empty, but then the validation is
726 # for environ based auth, password can be empty, but then the validation is
723 # on the server that fills in the env data needed for authentication
727 # on the server that fills in the env data needed for authentication
724 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
728 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
725
729
726 auth_time = time.time() - start
730 auth_time = time.time() - start
727 log.debug('Authentication for plugin `%s` completed in %.3fs, '
731 log.debug('Authentication for plugin `%s` completed in %.3fs, '
728 'expiration time of fetched cache %.1fs.',
732 'expiration time of fetched cache %.1fs.',
729 plugin.get_id(), auth_time, cache_ttl)
733 plugin.get_id(), auth_time, cache_ttl)
730
734
731 log.debug('PLUGIN USER DATA: %s', plugin_user)
735 log.debug('PLUGIN USER DATA: %s', plugin_user)
732
736
733 if plugin_user:
737 if plugin_user:
734 log.debug('Plugin returned proper authentication data')
738 log.debug('Plugin returned proper authentication data')
735 return plugin_user
739 return plugin_user
736 # we failed to Auth because .auth() method didn't return proper user
740 # we failed to Auth because .auth() method didn't return proper user
737 log.debug("User `%s` failed to authenticate against %s",
741 log.debug("User `%s` failed to authenticate against %s",
738 display_user, plugin.get_id())
742 display_user, plugin.get_id())
739
743
740 # case when we failed to authenticate against all defined plugins
744 # case when we failed to authenticate against all defined plugins
741 return None
745 return None
742
746
743
747
744 def chop_at(s, sub, inclusive=False):
748 def chop_at(s, sub, inclusive=False):
745 """Truncate string ``s`` at the first occurrence of ``sub``.
749 """Truncate string ``s`` at the first occurrence of ``sub``.
746
750
747 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
751 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
748
752
749 >>> chop_at("plutocratic brats", "rat")
753 >>> chop_at("plutocratic brats", "rat")
750 'plutoc'
754 'plutoc'
751 >>> chop_at("plutocratic brats", "rat", True)
755 >>> chop_at("plutocratic brats", "rat", True)
752 'plutocrat'
756 'plutocrat'
753 """
757 """
754 pos = s.find(sub)
758 pos = s.find(sub)
755 if pos == -1:
759 if pos == -1:
756 return s
760 return s
757 if inclusive:
761 if inclusive:
758 return s[:pos+len(sub)]
762 return s[:pos+len(sub)]
759 return s[:pos]
763 return s[:pos]
@@ -1,51 +1,52 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
22
23 from rhodecode.authentication import plugin_default_auth_ttl
23 from rhodecode.translation import _
24 from rhodecode.translation import _
24
25
25
26
26 class AuthnPluginSettingsSchemaBase(colander.MappingSchema):
27 class AuthnPluginSettingsSchemaBase(colander.MappingSchema):
27 """
28 """
28 This base schema is intended for use in authentication plugins.
29 This base schema is intended for use in authentication plugins.
29 It adds a few default settings (e.g., "enabled"), so that plugin
30 It adds a few default settings (e.g., "enabled"), so that plugin
30 authors don't have to maintain a bunch of boilerplate.
31 authors don't have to maintain a bunch of boilerplate.
31 """
32 """
32 enabled = colander.SchemaNode(
33 enabled = colander.SchemaNode(
33 colander.Bool(),
34 colander.Bool(),
34 default=False,
35 default=False,
35 description=_('Enable or disable this authentication plugin.'),
36 description=_('Enable or disable this authentication plugin.'),
36 missing=False,
37 missing=False,
37 title=_('Enabled'),
38 title=_('Enabled'),
38 widget='bool',
39 widget='bool',
39 )
40 )
40 cache_ttl = colander.SchemaNode(
41 cache_ttl = colander.SchemaNode(
41 colander.Int(),
42 colander.Int(),
42 default=0,
43 default=plugin_default_auth_ttl,
43 description=_('Amount of seconds to cache the authentication and '
44 description=_('Amount of seconds to cache the authentication and '
44 'permissions check response call for this plugin. \n'
45 'permissions check response call for this plugin. \n'
45 'Useful for expensive calls like LDAP to improve the '
46 'Useful for expensive calls like LDAP to improve the '
46 'performance of the system (0 means disabled).'),
47 'performance of the system (0 means disabled).'),
47 missing=0,
48 missing=0,
48 title=_('Auth Cache TTL'),
49 title=_('Auth Cache TTL'),
49 validator=colander.Range(min=0, max=None),
50 validator=colander.Range(min=0, max=None),
50 widget='int',
51 widget='int',
51 )
52 )
@@ -1,92 +1,89 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 pytest
22 import pytest
23
23
24
24
25 class EnabledAuthPlugin(object):
25 class EnabledAuthPlugin(object):
26 """
26 """
27 Context manager that updates the 'auth_plugins' setting in DB to enable
27 Context manager that updates the 'auth_plugins' setting in DB to enable
28 a plugin. Previous setting is restored on exit. The rhodecode auth plugin
28 a plugin. Previous setting is restored on exit. The rhodecode auth plugin
29 is also enabled because it is needed to login the test users.
29 is also enabled because it is needed to login the test users.
30 """
30 """
31
31
32 def __init__(self, plugin):
32 def __init__(self, plugin):
33 self.new_value = set([
33 self.new_value = {'egg:rhodecode-enterprise-ce#rhodecode', plugin.get_id()}
34 'egg:rhodecode-enterprise-ce#rhodecode',
35 plugin.get_id()
36 ])
37
34
38 def __enter__(self):
35 def __enter__(self):
39 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
40 self._old_value = SettingsModel().get_auth_plugins()
37 self._old_value = SettingsModel().get_auth_plugins()
41 SettingsModel().create_or_update_setting(
38 SettingsModel().create_or_update_setting(
42 'auth_plugins', ','.join(self.new_value))
39 'auth_plugins', ','.join(self.new_value))
43
40
44 def __exit__(self, type, value, traceback):
41 def __exit__(self, type, value, traceback):
45 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.settings import SettingsModel
46 SettingsModel().create_or_update_setting(
43 SettingsModel().create_or_update_setting(
47 'auth_plugins', ','.join(self._old_value))
44 'auth_plugins', ','.join(self._old_value))
48
45
49
46
50 class DisabledAuthPlugin():
47 class DisabledAuthPlugin(object):
51 """
48 """
52 Context manager that updates the 'auth_plugins' setting in DB to disable
49 Context manager that updates the 'auth_plugins' setting in DB to disable
53 a plugin. Previous setting is restored on exit.
50 a plugin. Previous setting is restored on exit.
54 """
51 """
55
52
56 def __init__(self, plugin):
53 def __init__(self, plugin):
57 self.plugin_id = plugin.get_id()
54 self.plugin_id = plugin.get_id()
58
55
59 def __enter__(self):
56 def __enter__(self):
60 from rhodecode.model.settings import SettingsModel
57 from rhodecode.model.settings import SettingsModel
61 self._old_value = SettingsModel().get_auth_plugins()
58 self._old_value = SettingsModel().get_auth_plugins()
62 new_value = [id_ for id_ in self._old_value if id_ != self.plugin_id]
59 new_value = [id_ for id_ in self._old_value if id_ != self.plugin_id]
63 SettingsModel().create_or_update_setting(
60 SettingsModel().create_or_update_setting(
64 'auth_plugins', ','.join(new_value))
61 'auth_plugins', ','.join(new_value))
65
62
66 def __exit__(self, type, value, traceback):
63 def __exit__(self, type, value, traceback):
67 from rhodecode.model.settings import SettingsModel
64 from rhodecode.model.settings import SettingsModel
68 SettingsModel().create_or_update_setting(
65 SettingsModel().create_or_update_setting(
69 'auth_plugins', ','.join(self._old_value))
66 'auth_plugins', ','.join(self._old_value))
70
67
71
68
72 @pytest.fixture(params=[
69 @pytest.fixture(params=[
73 ('rhodecode.authentication.plugins.auth_crowd', 'egg:rhodecode-enterprise-ce#crowd'),
70 ('rhodecode.authentication.plugins.auth_crowd', 'egg:rhodecode-enterprise-ce#crowd'),
74 ('rhodecode.authentication.plugins.auth_headers', 'egg:rhodecode-enterprise-ce#headers'),
71 ('rhodecode.authentication.plugins.auth_headers', 'egg:rhodecode-enterprise-ce#headers'),
75 ('rhodecode.authentication.plugins.auth_jasig_cas', 'egg:rhodecode-enterprise-ce#jasig_cas'),
72 ('rhodecode.authentication.plugins.auth_jasig_cas', 'egg:rhodecode-enterprise-ce#jasig_cas'),
76 ('rhodecode.authentication.plugins.auth_ldap', 'egg:rhodecode-enterprise-ce#ldap'),
73 ('rhodecode.authentication.plugins.auth_ldap', 'egg:rhodecode-enterprise-ce#ldap'),
77 ('rhodecode.authentication.plugins.auth_pam', 'egg:rhodecode-enterprise-ce#pam'),
74 ('rhodecode.authentication.plugins.auth_pam', 'egg:rhodecode-enterprise-ce#pam'),
78 ('rhodecode.authentication.plugins.auth_rhodecode', 'egg:rhodecode-enterprise-ce#rhodecode'),
75 ('rhodecode.authentication.plugins.auth_rhodecode', 'egg:rhodecode-enterprise-ce#rhodecode'),
79 ('rhodecode.authentication.plugins.auth_token', 'egg:rhodecode-enterprise-ce#token'),
76 ('rhodecode.authentication.plugins.auth_token', 'egg:rhodecode-enterprise-ce#token'),
80 ])
77 ])
81 def auth_plugin(request):
78 def auth_plugin(request):
82 """
79 """
83 Fixture that provides instance for each authentication plugin. These
80 Fixture that provides instance for each authentication plugin. These
84 instances are NOT the instances which are registered to the authentication
81 instances are NOT the instances which are registered to the authentication
85 registry.
82 registry.
86 """
83 """
87 from importlib import import_module
84 from importlib import import_module
88
85
89 # Create plugin instance.
86 # Create plugin instance.
90 module, plugin_id = request.param
87 module, plugin_id = request.param
91 plugin_module = import_module(module)
88 plugin_module = import_module(module)
92 return plugin_module.plugin_factory(plugin_id)
89 return plugin_module.plugin_factory(plugin_id)
@@ -1,81 +1,81 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
22 import os
21 import os
23 import logging
22 import logging
24 import rhodecode
23 import rhodecode
25
24
26
27 from rhodecode.config import utils
25 from rhodecode.config import utils
28
26
29 from rhodecode.lib.utils import load_rcextensions
27 from rhodecode.lib.utils import load_rcextensions
30 from rhodecode.lib.utils2 import str2bool
28 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.vcs import connect_vcs
29 from rhodecode.lib.vcs import connect_vcs
32
30
33 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
34
32
35
33
36 def load_pyramid_environment(global_config, settings):
34 def load_pyramid_environment(global_config, settings):
37 # Some parts of the code expect a merge of global and app settings.
35 # Some parts of the code expect a merge of global and app settings.
38 settings_merged = global_config.copy()
36 settings_merged = global_config.copy()
39 settings_merged.update(settings)
37 settings_merged.update(settings)
40
38
41 # TODO(marcink): probably not required anymore
39 # TODO(marcink): probably not required anymore
42 # configure channelstream,
40 # configure channelstream,
43 settings_merged['channelstream_config'] = {
41 settings_merged['channelstream_config'] = {
44 'enabled': str2bool(settings_merged.get('channelstream.enabled', False)),
42 'enabled': str2bool(settings_merged.get('channelstream.enabled', False)),
45 'server': settings_merged.get('channelstream.server'),
43 'server': settings_merged.get('channelstream.server'),
46 'secret': settings_merged.get('channelstream.secret')
44 'secret': settings_merged.get('channelstream.secret')
47 }
45 }
48
46
49 # If this is a test run we prepare the test environment like
47 # If this is a test run we prepare the test environment like
50 # creating a test database, test search index and test repositories.
48 # creating a test database, test search index and test repositories.
51 # This has to be done before the database connection is initialized.
49 # This has to be done before the database connection is initialized.
52 if settings['is_test']:
50 if settings['is_test']:
53 rhodecode.is_test = True
51 rhodecode.is_test = True
54 rhodecode.disable_error_handler = True
52 rhodecode.disable_error_handler = True
53 from rhodecode import authentication
54 authentication.plugin_default_auth_ttl = 0
55
55
56 utils.initialize_test_environment(settings_merged)
56 utils.initialize_test_environment(settings_merged)
57
57
58 # Initialize the database connection.
58 # Initialize the database connection.
59 utils.initialize_database(settings_merged)
59 utils.initialize_database(settings_merged)
60
60
61 load_rcextensions(root_path=settings_merged['here'])
61 load_rcextensions(root_path=settings_merged['here'])
62
62
63 # Limit backends to `vcs.backends` from configuration
63 # Limit backends to `vcs.backends` from configuration
64 for alias in rhodecode.BACKENDS.keys():
64 for alias in rhodecode.BACKENDS.keys():
65 if alias not in settings['vcs.backends']:
65 if alias not in settings['vcs.backends']:
66 del rhodecode.BACKENDS[alias]
66 del rhodecode.BACKENDS[alias]
67 log.info('Enabled VCS backends: %s', rhodecode.BACKENDS.keys())
67 log.info('Enabled VCS backends: %s', rhodecode.BACKENDS.keys())
68
68
69 # initialize vcs client and optionally run the server if enabled
69 # initialize vcs client and optionally run the server if enabled
70 vcs_server_uri = settings['vcs.server']
70 vcs_server_uri = settings['vcs.server']
71 vcs_server_enabled = settings['vcs.server.enable']
71 vcs_server_enabled = settings['vcs.server.enable']
72
72
73 utils.configure_vcs(settings)
73 utils.configure_vcs(settings)
74
74
75 # Store the settings to make them available to other modules.
75 # Store the settings to make them available to other modules.
76
76
77 rhodecode.PYRAMID_SETTINGS = settings_merged
77 rhodecode.PYRAMID_SETTINGS = settings_merged
78 rhodecode.CONFIG = settings_merged
78 rhodecode.CONFIG = settings_merged
79
79
80 if vcs_server_enabled:
80 if vcs_server_enabled:
81 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
81 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
General Comments 0
You need to be logged in to leave comments. Login now