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 | 36 | 8fbd8b0c3ddc2fa4ac9e4ca16942a03eb593df2d v4.11.5 |
|
37 | 37 | f0609aa5d5d05a1ca2f97c3995542236131c9d8a v4.11.6 |
|
38 | 38 | b5b30547d90d2e088472a70c84878f429ffbf40d v4.12.0 |
|
39 | 9072253aa8894d20c00b4a43dc61c2168c1eff94 v4.12.1 |
@@ -9,6 +9,7 b' Release Notes' | |||
|
9 | 9 | .. toctree:: |
|
10 | 10 | :maxdepth: 1 |
|
11 | 11 | |
|
12 | release-notes-4.12.1.rst | |
|
12 | 13 | release-notes-4.12.0.rst |
|
13 | 14 | release-notes-4.11.6.rst |
|
14 | 15 | release-notes-4.11.5.rst |
@@ -152,13 +152,14 b' class RepoFeedView(RepoAppView):' | |||
|
152 | 152 | return _generate_feed() |
|
153 | 153 | |
|
154 | 154 | if self.path_filter.is_enabled: |
|
155 | mime_type, feed = _generate_feed() | |
|
156 | else: | |
|
155 | 157 | invalidator_context = CacheKey.repo_context_cache( |
|
156 |
_generate_feed_and_cache, self.db_repo_name, |
|
|
158 | _generate_feed_and_cache, self.db_repo_name, | |
|
159 | CacheKey.CACHE_TYPE_ATOM) | |
|
157 | 160 | with invalidator_context as context: |
|
158 | 161 | context.invalidate() |
|
159 | 162 | mime_type, feed = context.compute() |
|
160 | else: | |
|
161 | mime_type, feed = _generate_feed() | |
|
162 | 163 | |
|
163 | 164 | response = Response(feed) |
|
164 | 165 | response.content_type = mime_type |
@@ -204,14 +205,15 b' class RepoFeedView(RepoAppView):' | |||
|
204 | 205 | return _generate_feed() |
|
205 | 206 | |
|
206 | 207 | if self.path_filter.is_enabled: |
|
208 | mime_type, feed = _generate_feed() | |
|
209 | else: | |
|
207 | 210 | invalidator_context = CacheKey.repo_context_cache( |
|
208 |
_generate_feed_and_cache, self.db_repo_name, |
|
|
211 | _generate_feed_and_cache, self.db_repo_name, | |
|
212 | CacheKey.CACHE_TYPE_RSS) | |
|
209 | 213 | |
|
210 | 214 | with invalidator_context as context: |
|
211 | 215 | context.invalidate() |
|
212 | 216 | mime_type, feed = context.compute() |
|
213 | else: | |
|
214 | mime_type, feed = _generate_feed() | |
|
215 | 217 | |
|
216 | 218 | response = Response(feed) |
|
217 | 219 | response.content_type = mime_type |
@@ -21,7 +21,8 b'' | |||
|
21 | 21 | """ |
|
22 | 22 | Authentication modules |
|
23 | 23 | """ |
|
24 | ||
|
24 | import socket | |
|
25 | import string | |
|
25 | 26 | import colander |
|
26 | 27 | import copy |
|
27 | 28 | import logging |
@@ -31,14 +32,13 b' import warnings' | |||
|
31 | 32 | import functools |
|
32 | 33 | |
|
33 | 34 | from pyramid.threadlocal import get_current_registry |
|
34 | from zope.cachedescriptors.property import Lazy as LazyProperty | |
|
35 | 35 | |
|
36 | 36 | from rhodecode.authentication.interface import IAuthnPluginRegistry |
|
37 | 37 | from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase |
|
38 | 38 | from rhodecode.lib import caches |
|
39 | 39 | from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt |
|
40 | from rhodecode.lib.utils2 import safe_int | |
|
41 |
from rhodecode.lib. |
|
|
40 | from rhodecode.lib.utils2 import safe_int, safe_str | |
|
41 | from rhodecode.lib.exceptions import LdapConnectionError | |
|
42 | 42 | from rhodecode.model.db import User |
|
43 | 43 | from rhodecode.model.meta import Session |
|
44 | 44 | from rhodecode.model.settings import SettingsModel |
@@ -556,6 +556,63 b' class RhodeCodeExternalAuthPlugin(RhodeC' | |||
|
556 | 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 | 616 | def loadplugin(plugin_id): |
|
560 | 617 | """ |
|
561 | 618 | Loads and returns an instantiated authentication plugin. |
@@ -613,7 +670,7 b' def authenticate(username, password, env' | |||
|
613 | 670 | authn_registry = get_authn_registry(registry) |
|
614 | 671 | plugins_to_check = authn_registry.get_plugins_for_authentication() |
|
615 | 672 | log.debug('Starting ordered authentication chain using %s plugins', |
|
616 | plugins_to_check) | |
|
673 | [x.name for x in plugins_to_check]) | |
|
617 | 674 | for plugin in plugins_to_check: |
|
618 | 675 | plugin.set_auth_type(auth_type) |
|
619 | 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 | 22 | RhodeCode authentication plugin for LDAP |
|
23 | 23 | """ |
|
24 | 24 | |
|
25 | import socket | |
|
26 | import re | |
|
27 | import colander | |
|
28 | 25 | import logging |
|
29 | 26 | import traceback |
|
30 | import string | |
|
31 | 27 | |
|
28 | import colander | |
|
32 | 29 | from rhodecode.translation import _ |
|
33 | 30 | from rhodecode.authentication.base import ( |
|
34 |
RhodeCodeExternalAuthPlugin, |
|
|
31 | RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property) | |
|
35 | 32 | from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase |
|
36 | 33 | from rhodecode.authentication.routes import AuthnPluginResourceBase |
|
37 | 34 | from rhodecode.lib.colander_utils import strip_whitespace |
@@ -200,40 +197,7 b' class LdapSettingsSchema(AuthnPluginSett' | |||
|
200 | 197 | widget='string') |
|
201 | 198 | |
|
202 | 199 | |
|
203 |
class AuthLdap( |
|
|
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]) | |
|
200 | class AuthLdap(AuthLdapBase): | |
|
237 | 201 | |
|
238 | 202 | def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', |
|
239 | 203 | tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, |
@@ -256,8 +220,9 b' class AuthLdap(object):' | |||
|
256 | 220 | OPT_X_TLS_DEMAND = 2 |
|
257 | 221 | self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, |
|
258 | 222 | OPT_X_TLS_DEMAND) |
|
223 | self.LDAP_SERVER = server | |
|
259 | 224 | # split server into list |
|
260 |
self.SERVER_ADDRESSES = server |
|
|
225 | self.SERVER_ADDRESSES = self._get_server_list(server) | |
|
261 | 226 | self.LDAP_SERVER_PORT = port |
|
262 | 227 | |
|
263 | 228 | # USE FOR READ ONLY BIND TO LDAP SERVER |
@@ -265,13 +230,12 b' class AuthLdap(object):' | |||
|
265 | 230 | |
|
266 | 231 | self.LDAP_BIND_DN = safe_str(bind_dn) |
|
267 | 232 | self.LDAP_BIND_PASS = safe_str(bind_pass) |
|
268 | self.LDAP_SERVER = self._build_servers() | |
|
233 | ||
|
269 | 234 | self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) |
|
270 | 235 | self.BASE_DN = safe_str(base_dn) |
|
271 | 236 | self.LDAP_FILTER = safe_str(ldap_filter) |
|
272 | 237 | |
|
273 | 238 | def _get_ldap_conn(self): |
|
274 | log.debug('initializing LDAP connection to:%s', self.LDAP_SERVER) | |
|
275 | 239 | |
|
276 | 240 | if self.debug: |
|
277 | 241 | ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) |
@@ -285,7 +249,10 b' class AuthLdap(object):' | |||
|
285 | 249 | ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) |
|
286 | 250 | |
|
287 | 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 | 256 | ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout) |
|
290 | 257 | ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout) |
|
291 | 258 | ldap_conn.timeout = self.timeout |
@@ -305,12 +272,6 b' class AuthLdap(object):' | |||
|
305 | 272 | |
|
306 | 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 | 275 | def fetch_attrs_from_simple_bind(self, server, dn, username, password): |
|
315 | 276 | try: |
|
316 | 277 | log.debug('Trying simple bind with %s', dn) |
@@ -336,7 +297,9 b' class AuthLdap(object):' | |||
|
336 | 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 | 304 | if not password: |
|
342 | 305 | msg = "Authenticating user %s with blank password not allowed" |
@@ -350,8 +313,8 b' class AuthLdap(object):' | |||
|
350 | 313 | ldap_conn = self._get_ldap_conn() |
|
351 | 314 | filter_ = '(&%s(%s=%s))' % ( |
|
352 | 315 | self.LDAP_FILTER, self.attr_login, username) |
|
353 | log.debug("Authenticating %r filter %s at %s", self.BASE_DN, | |
|
354 | filter_, self.LDAP_SERVER) | |
|
316 | log.debug( | |
|
317 | "Authenticating %r filter %s", self.BASE_DN, filter_) | |
|
355 | 318 | lobjects = ldap_conn.search_ext_s( |
|
356 | 319 | self.BASE_DN, self.SEARCH_SCOPE, filter_) |
|
357 | 320 |
@@ -57,9 +57,6 b' def trigger_post_push_hook(' | |||
|
57 | 57 | :param repo_alias: the type of SCM repo |
|
58 | 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 | 60 | extras = _get_rc_scm_extras(username, repo_name, repo_alias, action) |
|
64 | 61 | extras.commit_ids = commit_ids |
|
65 | 62 | hooks_base.post_push(extras) |
General Comments 0
You need to be logged in to leave comments.
Login now