##// END OF EJS Templates
release: merge back stable branch into default
marcink -
r2747:f690c910 merge default
parent child Browse files
Show More
@@ -0,0 +1,48 b''
1 |RCE| 4.12.1 |RNS|
2 ------------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2018-05-15
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18 - SVN: execute web-based hooks for SVN so integration framework work also via
19 web based editor.
20 - ReCaptcha: adjust for v2 that is the only left one supported since 1st of May.
21 - JIRA integration: add support for proxy server connection to JIRA server.
22
23
24 Security
25 ^^^^^^^^
26
27
28
29 Performance
30 ^^^^^^^^^^^
31
32
33
34 Fixes
35 ^^^^^
36
37 - SVN: make hooks safer and fully backward compatible. In certain old setups
38 new integration for SVN could make problems. We use a safer hooks now that
39 shouldn't break usage of older SVN and still provide required functionality
40 for integration framework
41 - LDAP: use connection ping only in case of single server.
42 - Repository feed: fix path-based permissions condition on caching element.
43
44
45 Upgrade notes
46 ^^^^^^^^^^^^^
47
48 - Scheduled release addressing found problems reported by users.
@@ -36,3 +36,4 b' 194c74f33e32bbae6fc4d71ec5a999cff3c13605'
36 8fbd8b0c3ddc2fa4ac9e4ca16942a03eb593df2d v4.11.5
36 8fbd8b0c3ddc2fa4ac9e4ca16942a03eb593df2d v4.11.5
37 f0609aa5d5d05a1ca2f97c3995542236131c9d8a v4.11.6
37 f0609aa5d5d05a1ca2f97c3995542236131c9d8a v4.11.6
38 b5b30547d90d2e088472a70c84878f429ffbf40d v4.12.0
38 b5b30547d90d2e088472a70c84878f429ffbf40d v4.12.0
39 9072253aa8894d20c00b4a43dc61c2168c1eff94 v4.12.1
@@ -9,6 +9,7 b' Release Notes'
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12 release-notes-4.12.1.rst
12 release-notes-4.12.0.rst
13 release-notes-4.12.0.rst
13 release-notes-4.11.6.rst
14 release-notes-4.11.6.rst
14 release-notes-4.11.5.rst
15 release-notes-4.11.5.rst
@@ -152,13 +152,14 b' class RepoFeedView(RepoAppView):'
152 return _generate_feed()
152 return _generate_feed()
153
153
154 if self.path_filter.is_enabled:
154 if self.path_filter.is_enabled:
155 mime_type, feed = _generate_feed()
156 else:
155 invalidator_context = CacheKey.repo_context_cache(
157 invalidator_context = CacheKey.repo_context_cache(
156 _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
158 _generate_feed_and_cache, self.db_repo_name,
159 CacheKey.CACHE_TYPE_ATOM)
157 with invalidator_context as context:
160 with invalidator_context as context:
158 context.invalidate()
161 context.invalidate()
159 mime_type, feed = context.compute()
162 mime_type, feed = context.compute()
160 else:
161 mime_type, feed = _generate_feed()
162
163
163 response = Response(feed)
164 response = Response(feed)
164 response.content_type = mime_type
165 response.content_type = mime_type
@@ -204,14 +205,15 b' class RepoFeedView(RepoAppView):'
204 return _generate_feed()
205 return _generate_feed()
205
206
206 if self.path_filter.is_enabled:
207 if self.path_filter.is_enabled:
208 mime_type, feed = _generate_feed()
209 else:
207 invalidator_context = CacheKey.repo_context_cache(
210 invalidator_context = CacheKey.repo_context_cache(
208 _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
211 _generate_feed_and_cache, self.db_repo_name,
212 CacheKey.CACHE_TYPE_RSS)
209
213
210 with invalidator_context as context:
214 with invalidator_context as context:
211 context.invalidate()
215 context.invalidate()
212 mime_type, feed = context.compute()
216 mime_type, feed = context.compute()
213 else:
214 mime_type, feed = _generate_feed()
215
217
216 response = Response(feed)
218 response = Response(feed)
217 response.content_type = mime_type
219 response.content_type = mime_type
@@ -21,7 +21,8 b''
21 """
21 """
22 Authentication modules
22 Authentication modules
23 """
23 """
24
24 import socket
25 import string
25 import colander
26 import colander
26 import copy
27 import copy
27 import logging
28 import logging
@@ -31,14 +32,13 b' import warnings'
31 import functools
32 import functools
32
33
33 from pyramid.threadlocal import get_current_registry
34 from pyramid.threadlocal import get_current_registry
34 from zope.cachedescriptors.property import Lazy as LazyProperty
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 caches
38 from rhodecode.lib import caches
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
40 from rhodecode.lib.utils2 import safe_int
40 from rhodecode.lib.utils2 import safe_int, safe_str
41 from rhodecode.lib.utils2 import safe_str
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
@@ -556,6 +556,63 b' class RhodeCodeExternalAuthPlugin(RhodeC'
556 return auth
556 return auth
557
557
558
558
559 class AuthLdapBase(object):
560
561 @classmethod
562 def _build_servers(cls, ldap_server_type, ldap_server, port):
563 def host_resolver(host, port, full_resolve=True):
564 """
565 Main work for this function is to prevent ldap connection issues,
566 and detect them early using a "greenified" sockets
567 """
568 host = host.strip()
569 if not full_resolve:
570 return '{}:{}'.format(host, port)
571
572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
573 try:
574 ip = socket.gethostbyname(host)
575 log.debug('Got LDAP server %s ip %s', host, ip)
576 except Exception:
577 raise LdapConnectionError(
578 'Failed to resolve host: `{}`'.format(host))
579
580 log.debug('LDAP: Checking if IP %s is accessible', ip)
581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
582 try:
583 s.connect((ip, int(port)))
584 s.shutdown(socket.SHUT_RD)
585 except Exception:
586 raise LdapConnectionError(
587 'Failed to connect to host: `{}:{}`'.format(host, port))
588
589 return '{}:{}'.format(host, port)
590
591 if len(ldap_server) == 1:
592 # in case of single server use resolver to detect potential
593 # connection issues
594 full_resolve = True
595 else:
596 full_resolve = False
597
598 return ', '.join(
599 ["{}://{}".format(
600 ldap_server_type,
601 host_resolver(host, port, full_resolve=full_resolve))
602 for host in ldap_server])
603
604 @classmethod
605 def _get_server_list(cls, servers):
606 return map(string.strip, servers.split(','))
607
608 @classmethod
609 def get_uid(cls, username, server_addresses):
610 uid = username
611 for server_addr in server_addresses:
612 uid = chop_at(username, "@%s" % server_addr)
613 return uid
614
615
559 def loadplugin(plugin_id):
616 def loadplugin(plugin_id):
560 """
617 """
561 Loads and returns an instantiated authentication plugin.
618 Loads and returns an instantiated authentication plugin.
@@ -613,7 +670,7 b' def authenticate(username, password, env'
613 authn_registry = get_authn_registry(registry)
670 authn_registry = get_authn_registry(registry)
614 plugins_to_check = authn_registry.get_plugins_for_authentication()
671 plugins_to_check = authn_registry.get_plugins_for_authentication()
615 log.debug('Starting ordered authentication chain using %s plugins',
672 log.debug('Starting ordered authentication chain using %s plugins',
616 plugins_to_check)
673 [x.name for x in plugins_to_check])
617 for plugin in plugins_to_check:
674 for plugin in plugins_to_check:
618 plugin.set_auth_type(auth_type)
675 plugin.set_auth_type(auth_type)
619 plugin.set_calling_scope_repo(acl_repo_name)
676 plugin.set_calling_scope_repo(acl_repo_name)
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -22,16 +22,13 b''
22 RhodeCode authentication plugin for LDAP
22 RhodeCode authentication plugin for LDAP
23 """
23 """
24
24
25 import socket
26 import re
27 import colander
28 import logging
25 import logging
29 import traceback
26 import traceback
30 import string
31
27
28 import colander
32 from rhodecode.translation import _
29 from rhodecode.translation import _
33 from rhodecode.authentication.base import (
30 from rhodecode.authentication.base import (
34 RhodeCodeExternalAuthPlugin, chop_at, hybrid_property)
31 RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property)
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
32 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.routes import AuthnPluginResourceBase
33 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.lib.colander_utils import strip_whitespace
34 from rhodecode.lib.colander_utils import strip_whitespace
@@ -200,40 +197,7 b' class LdapSettingsSchema(AuthnPluginSett'
200 widget='string')
197 widget='string')
201
198
202
199
203 class AuthLdap(object):
200 class AuthLdap(AuthLdapBase):
204
205 def _build_servers(self):
206 def host_resolver(host, port):
207 """
208 Main work for this function is to prevent ldap connection issues,
209 and detect them early using a "greenified" sockets
210 """
211 host = host.strip()
212
213 log.info('Resolving LDAP host %s', host)
214 try:
215 ip = socket.gethostbyname(host)
216 log.info('Got LDAP server %s ip %s', host, ip)
217 except Exception:
218 raise LdapConnectionError(
219 'Failed to resolve host: `{}`'.format(host))
220
221 log.info('Checking LDAP IP access %s', ip)
222 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
223 try:
224 s.connect((ip, int(port)))
225 s.shutdown(socket.SHUT_RD)
226 except Exception:
227 raise LdapConnectionError(
228 'Failed to connect to host: `{}:{}`'.format(host, port))
229
230 return '{}:{}'.format(host, port)
231
232 port = self.LDAP_SERVER_PORT
233 return ', '.join(
234 ["{}://{}".format(
235 self.ldap_server_type, host_resolver(host, port))
236 for host in self.SERVER_ADDRESSES])
237
201
238 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
202 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
239 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
203 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
@@ -256,8 +220,9 b' class AuthLdap(object):'
256 OPT_X_TLS_DEMAND = 2
220 OPT_X_TLS_DEMAND = 2
257 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
221 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
258 OPT_X_TLS_DEMAND)
222 OPT_X_TLS_DEMAND)
223 self.LDAP_SERVER = server
259 # split server into list
224 # split server into list
260 self.SERVER_ADDRESSES = server.split(',')
225 self.SERVER_ADDRESSES = self._get_server_list(server)
261 self.LDAP_SERVER_PORT = port
226 self.LDAP_SERVER_PORT = port
262
227
263 # USE FOR READ ONLY BIND TO LDAP SERVER
228 # USE FOR READ ONLY BIND TO LDAP SERVER
@@ -265,13 +230,12 b' class AuthLdap(object):'
265
230
266 self.LDAP_BIND_DN = safe_str(bind_dn)
231 self.LDAP_BIND_DN = safe_str(bind_dn)
267 self.LDAP_BIND_PASS = safe_str(bind_pass)
232 self.LDAP_BIND_PASS = safe_str(bind_pass)
268 self.LDAP_SERVER = self._build_servers()
233
269 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
234 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
270 self.BASE_DN = safe_str(base_dn)
235 self.BASE_DN = safe_str(base_dn)
271 self.LDAP_FILTER = safe_str(ldap_filter)
236 self.LDAP_FILTER = safe_str(ldap_filter)
272
237
273 def _get_ldap_conn(self):
238 def _get_ldap_conn(self):
274 log.debug('initializing LDAP connection to:%s', self.LDAP_SERVER)
275
239
276 if self.debug:
240 if self.debug:
277 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
241 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
@@ -285,7 +249,10 b' class AuthLdap(object):'
285 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
249 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
286
250
287 # init connection now
251 # init connection now
288 ldap_conn = ldap.initialize(self.LDAP_SERVER)
252 ldap_servers = self._build_servers(
253 self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
254 log.debug('initializing LDAP connection to:%s', ldap_servers)
255 ldap_conn = ldap.initialize(ldap_servers)
289 ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
256 ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
290 ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
257 ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
291 ldap_conn.timeout = self.timeout
258 ldap_conn.timeout = self.timeout
@@ -305,12 +272,6 b' class AuthLdap(object):'
305
272
306 return ldap_conn
273 return ldap_conn
307
274
308 def get_uid(self, username):
309 uid = username
310 for server_addr in self.SERVER_ADDRESSES:
311 uid = chop_at(username, "@%s" % server_addr)
312 return uid
313
314 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
275 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
315 try:
276 try:
316 log.debug('Trying simple bind with %s', dn)
277 log.debug('Trying simple bind with %s', dn)
@@ -336,7 +297,9 b' class AuthLdap(object):'
336 :param password: password
297 :param password: password
337 """
298 """
338
299
339 uid = self.get_uid(username)
300 uid = self.get_uid(username, self.SERVER_ADDRESSES)
301 user_attrs = {}
302 dn = ''
340
303
341 if not password:
304 if not password:
342 msg = "Authenticating user %s with blank password not allowed"
305 msg = "Authenticating user %s with blank password not allowed"
@@ -350,8 +313,8 b' class AuthLdap(object):'
350 ldap_conn = self._get_ldap_conn()
313 ldap_conn = self._get_ldap_conn()
351 filter_ = '(&%s(%s=%s))' % (
314 filter_ = '(&%s(%s=%s))' % (
352 self.LDAP_FILTER, self.attr_login, username)
315 self.LDAP_FILTER, self.attr_login, username)
353 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
316 log.debug(
354 filter_, self.LDAP_SERVER)
317 "Authenticating %r filter %s", self.BASE_DN, filter_)
355 lobjects = ldap_conn.search_ext_s(
318 lobjects = ldap_conn.search_ext_s(
356 self.BASE_DN, self.SEARCH_SCOPE, filter_)
319 self.BASE_DN, self.SEARCH_SCOPE, filter_)
357
320
@@ -57,9 +57,6 b' def trigger_post_push_hook('
57 :param repo_alias: the type of SCM repo
57 :param repo_alias: the type of SCM repo
58 :param commit_ids: list of commit ids that we pushed
58 :param commit_ids: list of commit ids that we pushed
59 """
59 """
60 if repo_alias not in ('hg', 'git'):
61 return
62
63 extras = _get_rc_scm_extras(username, repo_name, repo_alias, action)
60 extras = _get_rc_scm_extras(username, repo_name, repo_alias, action)
64 extras.commit_ids = commit_ids
61 extras.commit_ids = commit_ids
65 hooks_base.post_push(extras)
62 hooks_base.post_push(extras)
General Comments 0
You need to be logged in to leave comments. Login now