##// END OF EJS Templates
release: merge back stable branch into default
marcink -
r210:57b11684 merge default
parent child Browse files
Show More
@@ -0,0 +1,16 b''
1 |RCE| 4.1.2 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2016-06-16
8
9 Fixes
10 ^^^^^
11
12 - ssl: fixed http middleware so it works correctly with pyramid views. This
13 fixed http -> https redirection problems on login.
14
15 - ldap: fixed ldap usergroup authentication plugin so after upgrade it's
16 possible to change the settings again (EE only).
@@ -1,4 +1,5 b''
1 1bd3e92b7e2e2d2024152b34bb88dff1db544a71 v4.0.0
1 1bd3e92b7e2e2d2024152b34bb88dff1db544a71 v4.0.0
2 170c5398320ea6cddd50955e88d408794c21d43a v4.0.1
2 170c5398320ea6cddd50955e88d408794c21d43a v4.0.1
3 c3fe200198f5aa34cf2e4066df2881a9cefe3704 v4.1.0
3 c3fe200198f5aa34cf2e4066df2881a9cefe3704 v4.1.0
4 7fd5c850745e2ea821fb4406af5f4bff9b0a7526 v4.1.1
4 7fd5c850745e2ea821fb4406af5f4bff9b0a7526 v4.1.1
5 41c87da28a179953df86061d817bc35533c66dd2 v4.1.2
@@ -1,81 +1,82 b''
1 .. _rhodecode-release-notes-ref:
1 .. _rhodecode-release-notes-ref:
2
2
3 Release Notes
3 Release Notes
4 =============
4 =============
5
5
6 |RCE| 4.x Versions
6 |RCE| 4.x Versions
7 ------------------
7 ------------------
8
8
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12 release-notes-4.1.2.rst
12 release-notes-4.1.1.rst
13 release-notes-4.1.1.rst
13 release-notes-4.1.0.rst
14 release-notes-4.1.0.rst
14 release-notes-4.0.1.rst
15 release-notes-4.0.1.rst
15 release-notes-4.0.0.rst
16 release-notes-4.0.0.rst
16
17
17 |RCE| 3.x Versions
18 |RCE| 3.x Versions
18 ------------------
19 ------------------
19
20
20 .. toctree::
21 .. toctree::
21 :maxdepth: 1
22 :maxdepth: 1
22
23
23 release-notes-3.8.4.rst
24 release-notes-3.8.4.rst
24 release-notes-3.8.3.rst
25 release-notes-3.8.3.rst
25 release-notes-3.8.2.rst
26 release-notes-3.8.2.rst
26 release-notes-3.8.1.rst
27 release-notes-3.8.1.rst
27 release-notes-3.8.0.rst
28 release-notes-3.8.0.rst
28 release-notes-3.7.1.rst
29 release-notes-3.7.1.rst
29 release-notes-3.7.0.rst
30 release-notes-3.7.0.rst
30 release-notes-3.6.1.rst
31 release-notes-3.6.1.rst
31 release-notes-3.6.0.rst
32 release-notes-3.6.0.rst
32 release-notes-3.5.2.rst
33 release-notes-3.5.2.rst
33 release-notes-3.5.1.rst
34 release-notes-3.5.1.rst
34 release-notes-3.5.0.rst
35 release-notes-3.5.0.rst
35 release-notes-3.4.1.rst
36 release-notes-3.4.1.rst
36 release-notes-3.4.0.rst
37 release-notes-3.4.0.rst
37 release-notes-3.3.4.rst
38 release-notes-3.3.4.rst
38 release-notes-3.3.3.rst
39 release-notes-3.3.3.rst
39 release-notes-3.3.2.rst
40 release-notes-3.3.2.rst
40 release-notes-3.3.1.rst
41 release-notes-3.3.1.rst
41 release-notes-3.3.0.rst
42 release-notes-3.3.0.rst
42 release-notes-3.2.3.rst
43 release-notes-3.2.3.rst
43 release-notes-3.2.2.rst
44 release-notes-3.2.2.rst
44 release-notes-3.2.1.rst
45 release-notes-3.2.1.rst
45 release-notes-3.2.0.rst
46 release-notes-3.2.0.rst
46 release-notes-3.1.1.rst
47 release-notes-3.1.1.rst
47 release-notes-3.1.0.rst
48 release-notes-3.1.0.rst
48 release-notes-3.0.2.rst
49 release-notes-3.0.2.rst
49 release-notes-3.0.1.rst
50 release-notes-3.0.1.rst
50 release-notes-3.0.0.rst
51 release-notes-3.0.0.rst
51
52
52 |RCE| 2.x Versions
53 |RCE| 2.x Versions
53 ------------------
54 ------------------
54
55
55 .. toctree::
56 .. toctree::
56 :maxdepth: 1
57 :maxdepth: 1
57
58
58 release-notes-2.2.8.rst
59 release-notes-2.2.8.rst
59 release-notes-2.2.7.rst
60 release-notes-2.2.7.rst
60 release-notes-2.2.6.rst
61 release-notes-2.2.6.rst
61 release-notes-2.2.5.rst
62 release-notes-2.2.5.rst
62 release-notes-2.2.4.rst
63 release-notes-2.2.4.rst
63 release-notes-2.2.3.rst
64 release-notes-2.2.3.rst
64 release-notes-2.2.2.rst
65 release-notes-2.2.2.rst
65 release-notes-2.2.1.rst
66 release-notes-2.2.1.rst
66 release-notes-2.2.0.rst
67 release-notes-2.2.0.rst
67 release-notes-2.1.0.rst
68 release-notes-2.1.0.rst
68 release-notes-2.0.2.rst
69 release-notes-2.0.2.rst
69 release-notes-2.0.1.rst
70 release-notes-2.0.1.rst
70 release-notes-2.0.0.rst
71 release-notes-2.0.0.rst
71
72
72 |RCE| 1.x Versions
73 |RCE| 1.x Versions
73 ------------------
74 ------------------
74
75
75 .. toctree::
76 .. toctree::
76 :maxdepth: 1
77 :maxdepth: 1
77
78
78 release-notes-1.7.2.rst
79 release-notes-1.7.2.rst
79 release-notes-1.7.1.rst
80 release-notes-1.7.1.rst
80 release-notes-1.7.0.rst
81 release-notes-1.7.0.rst
81 release-notes-1.6.0.rst
82 release-notes-1.6.0.rst
@@ -1,386 +1,387 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons middleware initialization
22 Pylons middleware initialization
23 """
23 """
24 import logging
24 import logging
25
25
26 from paste.registry import RegistryManager
26 from paste.registry import RegistryManager
27 from paste.gzipper import make_gzip_middleware
27 from paste.gzipper import make_gzip_middleware
28 from pylons.wsgiapp import PylonsApp
28 from pylons.wsgiapp import PylonsApp
29 from pyramid.authorization import ACLAuthorizationPolicy
29 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid.config import Configurator
30 from pyramid.config import Configurator
31 from pyramid.static import static_view
31 from pyramid.static import static_view
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
35 import pyramid.httpexceptions as httpexceptions
35 import pyramid.httpexceptions as httpexceptions
36 from pyramid.renderers import render_to_response, render
36 from pyramid.renderers import render_to_response, render
37 from routes.middleware import RoutesMiddleware
37 from routes.middleware import RoutesMiddleware
38 import routes.util
38 import routes.util
39
39
40 import rhodecode
40 import rhodecode
41 from rhodecode.config import patches
41 from rhodecode.config import patches
42 from rhodecode.config.environment import (
42 from rhodecode.config.environment import (
43 load_environment, load_pyramid_environment)
43 load_environment, load_pyramid_environment)
44 from rhodecode.lib.middleware import csrf
44 from rhodecode.lib.middleware import csrf
45 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
45 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
46 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
46 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
48 from rhodecode.lib.middleware.vcs import VCSMiddleware
48 from rhodecode.lib.middleware.vcs import VCSMiddleware
49 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
49 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
50
50
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
55 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
56 """Create a Pylons WSGI application and return it
56 """Create a Pylons WSGI application and return it
57
57
58 ``global_conf``
58 ``global_conf``
59 The inherited configuration for this application. Normally from
59 The inherited configuration for this application. Normally from
60 the [DEFAULT] section of the Paste ini file.
60 the [DEFAULT] section of the Paste ini file.
61
61
62 ``full_stack``
62 ``full_stack``
63 Whether or not this application provides a full WSGI stack (by
63 Whether or not this application provides a full WSGI stack (by
64 default, meaning it handles its own exceptions and errors).
64 default, meaning it handles its own exceptions and errors).
65 Disable full_stack when this application is "managed" by
65 Disable full_stack when this application is "managed" by
66 another WSGI middleware.
66 another WSGI middleware.
67
67
68 ``app_conf``
68 ``app_conf``
69 The application's local configuration. Normally specified in
69 The application's local configuration. Normally specified in
70 the [app:<name>] section of the Paste ini file (where <name>
70 the [app:<name>] section of the Paste ini file (where <name>
71 defaults to main).
71 defaults to main).
72
72
73 """
73 """
74 # Apply compatibility patches
74 # Apply compatibility patches
75 patches.kombu_1_5_1_python_2_7_11()
75 patches.kombu_1_5_1_python_2_7_11()
76 patches.inspect_getargspec()
76 patches.inspect_getargspec()
77
77
78 # Configure the Pylons environment
78 # Configure the Pylons environment
79 config = load_environment(global_conf, app_conf)
79 config = load_environment(global_conf, app_conf)
80
80
81 # The Pylons WSGI app
81 # The Pylons WSGI app
82 app = PylonsApp(config=config)
82 app = PylonsApp(config=config)
83 if rhodecode.is_test:
83 if rhodecode.is_test:
84 app = csrf.CSRFDetector(app)
84 app = csrf.CSRFDetector(app)
85
85
86 expected_origin = config.get('expected_origin')
86 expected_origin = config.get('expected_origin')
87 if expected_origin:
87 if expected_origin:
88 # The API can be accessed from other Origins.
88 # The API can be accessed from other Origins.
89 app = csrf.OriginChecker(app, expected_origin,
89 app = csrf.OriginChecker(app, expected_origin,
90 skip_urls=[routes.util.url_for('api')])
90 skip_urls=[routes.util.url_for('api')])
91
91
92
92
93 if asbool(full_stack):
93 if asbool(full_stack):
94
94
95 # Appenlight monitoring and error handler
95 # Appenlight monitoring and error handler
96 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
96 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
97
97
98 # we want our low level middleware to get to the request ASAP. We don't
98 # we want our low level middleware to get to the request ASAP. We don't
99 # need any pylons stack middleware in them
99 # need any pylons stack middleware in them
100 app = VCSMiddleware(app, config, appenlight_client)
100 app = VCSMiddleware(app, config, appenlight_client)
101
101
102 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
103 app = HttpsFixup(app, config)
104
105 # Establish the Registry for this application
102 # Establish the Registry for this application
106 app = RegistryManager(app)
103 app = RegistryManager(app)
107
104
108 app.config = config
105 app.config = config
109
106
110 return app
107 return app
111
108
112
109
113 def make_pyramid_app(global_config, **settings):
110 def make_pyramid_app(global_config, **settings):
114 """
111 """
115 Constructs the WSGI application based on Pyramid and wraps the Pylons based
112 Constructs the WSGI application based on Pyramid and wraps the Pylons based
116 application.
113 application.
117
114
118 Specials:
115 Specials:
119
116
120 * We migrate from Pylons to Pyramid. While doing this, we keep both
117 * We migrate from Pylons to Pyramid. While doing this, we keep both
121 frameworks functional. This involves moving some WSGI middlewares around
118 frameworks functional. This involves moving some WSGI middlewares around
122 and providing access to some data internals, so that the old code is
119 and providing access to some data internals, so that the old code is
123 still functional.
120 still functional.
124
121
125 * The application can also be integrated like a plugin via the call to
122 * The application can also be integrated like a plugin via the call to
126 `includeme`. This is accompanied with the other utility functions which
123 `includeme`. This is accompanied with the other utility functions which
127 are called. Changing this should be done with great care to not break
124 are called. Changing this should be done with great care to not break
128 cases when these fragments are assembled from another place.
125 cases when these fragments are assembled from another place.
129
126
130 """
127 """
131 # The edition string should be available in pylons too, so we add it here
128 # The edition string should be available in pylons too, so we add it here
132 # before copying the settings.
129 # before copying the settings.
133 settings.setdefault('rhodecode.edition', 'Community Edition')
130 settings.setdefault('rhodecode.edition', 'Community Edition')
134
131
135 # As long as our Pylons application does expect "unprepared" settings, make
132 # As long as our Pylons application does expect "unprepared" settings, make
136 # sure that we keep an unmodified copy. This avoids unintentional change of
133 # sure that we keep an unmodified copy. This avoids unintentional change of
137 # behavior in the old application.
134 # behavior in the old application.
138 settings_pylons = settings.copy()
135 settings_pylons = settings.copy()
139
136
140 sanitize_settings_and_apply_defaults(settings)
137 sanitize_settings_and_apply_defaults(settings)
141 config = Configurator(settings=settings)
138 config = Configurator(settings=settings)
142 add_pylons_compat_data(config.registry, global_config, settings_pylons)
139 add_pylons_compat_data(config.registry, global_config, settings_pylons)
143
140
144 load_pyramid_environment(global_config, settings)
141 load_pyramid_environment(global_config, settings)
145
142
146 includeme(config)
143 includeme(config)
147 includeme_last(config)
144 includeme_last(config)
148 pyramid_app = config.make_wsgi_app()
145 pyramid_app = config.make_wsgi_app()
149 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
146 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
150 return pyramid_app
147 return pyramid_app
151
148
152
149
153 def add_pylons_compat_data(registry, global_config, settings):
150 def add_pylons_compat_data(registry, global_config, settings):
154 """
151 """
155 Attach data to the registry to support the Pylons integration.
152 Attach data to the registry to support the Pylons integration.
156 """
153 """
157 registry._pylons_compat_global_config = global_config
154 registry._pylons_compat_global_config = global_config
158 registry._pylons_compat_settings = settings
155 registry._pylons_compat_settings = settings
159
156
160
157
161 def webob_to_pyramid_http_response(webob_response):
158 def webob_to_pyramid_http_response(webob_response):
162 ResponseClass = httpexceptions.status_map[webob_response.status_int]
159 ResponseClass = httpexceptions.status_map[webob_response.status_int]
163 pyramid_response = ResponseClass(webob_response.status)
160 pyramid_response = ResponseClass(webob_response.status)
164 pyramid_response.status = webob_response.status
161 pyramid_response.status = webob_response.status
165 pyramid_response.headers.update(webob_response.headers)
162 pyramid_response.headers.update(webob_response.headers)
166 if pyramid_response.headers['content-type'] == 'text/html':
163 if pyramid_response.headers['content-type'] == 'text/html':
167 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
164 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
168 return pyramid_response
165 return pyramid_response
169
166
170
167
171 def error_handler(exception, request):
168 def error_handler(exception, request):
172 # TODO: dan: replace the old pylons error controller with this
169 # TODO: dan: replace the old pylons error controller with this
173 from rhodecode.model.settings import SettingsModel
170 from rhodecode.model.settings import SettingsModel
174 from rhodecode.lib.utils2 import AttributeDict
171 from rhodecode.lib.utils2 import AttributeDict
175
172
176 try:
173 try:
177 rc_config = SettingsModel().get_all_settings()
174 rc_config = SettingsModel().get_all_settings()
178 except Exception:
175 except Exception:
179 log.exception('failed to fetch settings')
176 log.exception('failed to fetch settings')
180 rc_config = {}
177 rc_config = {}
181
178
182 base_response = HTTPInternalServerError()
179 base_response = HTTPInternalServerError()
183 # prefer original exception for the response since it may have headers set
180 # prefer original exception for the response since it may have headers set
184 if isinstance(exception, HTTPError):
181 if isinstance(exception, HTTPError):
185 base_response = exception
182 base_response = exception
186
183
187 c = AttributeDict()
184 c = AttributeDict()
188 c.error_message = base_response.status
185 c.error_message = base_response.status
189 c.error_explanation = base_response.explanation or str(base_response)
186 c.error_explanation = base_response.explanation or str(base_response)
190 c.visual = AttributeDict()
187 c.visual = AttributeDict()
191
188
192 c.visual.rhodecode_support_url = (
189 c.visual.rhodecode_support_url = (
193 request.registry.settings.get('rhodecode_support_url') or
190 request.registry.settings.get('rhodecode_support_url') or
194 request.route_url('rhodecode_support')
191 request.route_url('rhodecode_support')
195 )
192 )
196 c.redirect_time = 0
193 c.redirect_time = 0
197 c.rhodecode_name = rc_config.get('rhodecode_title', '')
194 c.rhodecode_name = rc_config.get('rhodecode_title', '')
198 if not c.rhodecode_name:
195 if not c.rhodecode_name:
199 c.rhodecode_name = 'Rhodecode'
196 c.rhodecode_name = 'Rhodecode'
200
197
201 response = render_to_response(
198 response = render_to_response(
202 '/errors/error_document.html', {'c': c}, request=request,
199 '/errors/error_document.html', {'c': c}, request=request,
203 response=base_response)
200 response=base_response)
204
201
205 return response
202 return response
206
203
207
204
208 def includeme(config):
205 def includeme(config):
209 settings = config.registry.settings
206 settings = config.registry.settings
210
207
211 if asbool(settings.get('appenlight', 'false')):
208 if asbool(settings.get('appenlight', 'false')):
212 config.include('appenlight_client.ext.pyramid_tween')
209 config.include('appenlight_client.ext.pyramid_tween')
213
210
214 # Includes which are required. The application would fail without them.
211 # Includes which are required. The application would fail without them.
215 config.include('pyramid_mako')
212 config.include('pyramid_mako')
216 config.include('pyramid_beaker')
213 config.include('pyramid_beaker')
217 config.include('rhodecode.admin')
214 config.include('rhodecode.admin')
218 config.include('rhodecode.authentication')
215 config.include('rhodecode.authentication')
219 config.include('rhodecode.login')
216 config.include('rhodecode.login')
220 config.include('rhodecode.tweens')
217 config.include('rhodecode.tweens')
221 config.include('rhodecode.api')
218 config.include('rhodecode.api')
222 config.add_route(
219 config.add_route(
223 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
220 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
224
221
225 # Set the authorization policy.
222 # Set the authorization policy.
226 authz_policy = ACLAuthorizationPolicy()
223 authz_policy = ACLAuthorizationPolicy()
227 config.set_authorization_policy(authz_policy)
224 config.set_authorization_policy(authz_policy)
228
225
229 # Set the default renderer for HTML templates to mako.
226 # Set the default renderer for HTML templates to mako.
230 config.add_mako_renderer('.html')
227 config.add_mako_renderer('.html')
231
228
232 # plugin information
229 # plugin information
233 config.registry.rhodecode_plugins = {}
230 config.registry.rhodecode_plugins = {}
234
231
235 config.add_directive(
232 config.add_directive(
236 'register_rhodecode_plugin', register_rhodecode_plugin)
233 'register_rhodecode_plugin', register_rhodecode_plugin)
237 # include RhodeCode plugins
234 # include RhodeCode plugins
238 includes = aslist(settings.get('rhodecode.includes', []))
235 includes = aslist(settings.get('rhodecode.includes', []))
239 for inc in includes:
236 for inc in includes:
240 config.include(inc)
237 config.include(inc)
241
238
242 pylons_app = make_app(
239 pylons_app = make_app(
243 config.registry._pylons_compat_global_config,
240 config.registry._pylons_compat_global_config,
244 **config.registry._pylons_compat_settings)
241 **config.registry._pylons_compat_settings)
245 config.registry._pylons_compat_config = pylons_app.config
242 config.registry._pylons_compat_config = pylons_app.config
246
243
247 pylons_app_as_view = wsgiapp(pylons_app)
244 pylons_app_as_view = wsgiapp(pylons_app)
248
245
249 # Protect from VCS Server error related pages when server is not available
246 # Protect from VCS Server error related pages when server is not available
250 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
247 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
251 if not vcs_server_enabled:
248 if not vcs_server_enabled:
252 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
249 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
253
250
254
251
255 def pylons_app_with_error_handler(context, request):
252 def pylons_app_with_error_handler(context, request):
256 """
253 """
257 Handle exceptions from rc pylons app:
254 Handle exceptions from rc pylons app:
258
255
259 - old webob type exceptions get converted to pyramid exceptions
256 - old webob type exceptions get converted to pyramid exceptions
260 - pyramid exceptions are passed to the error handler view
257 - pyramid exceptions are passed to the error handler view
261 """
258 """
262 try:
259 try:
263 response = pylons_app_as_view(context, request)
260 response = pylons_app_as_view(context, request)
264 if 400 <= response.status_int <= 599: # webob type error responses
261 if 400 <= response.status_int <= 599: # webob type error responses
265 return error_handler(
262 return error_handler(
266 webob_to_pyramid_http_response(response), request)
263 webob_to_pyramid_http_response(response), request)
267 except HTTPError as e: # pyramid type exceptions
264 except HTTPError as e: # pyramid type exceptions
268 return error_handler(e, request)
265 return error_handler(e, request)
269 except Exception:
266 except Exception:
270 if settings.get('debugtoolbar.enabled', False):
267 if settings.get('debugtoolbar.enabled', False):
271 raise
268 raise
272 return error_handler(HTTPInternalServerError(), request)
269 return error_handler(HTTPInternalServerError(), request)
273 return response
270 return response
274
271
275 # This is the glue which allows us to migrate in chunks. By registering the
272 # This is the glue which allows us to migrate in chunks. By registering the
276 # pylons based application as the "Not Found" view in Pyramid, we will
273 # pylons based application as the "Not Found" view in Pyramid, we will
277 # fallback to the old application each time the new one does not yet know
274 # fallback to the old application each time the new one does not yet know
278 # how to handle a request.
275 # how to handle a request.
279 config.add_notfound_view(pylons_app_with_error_handler)
276 config.add_notfound_view(pylons_app_with_error_handler)
280
277
281 if settings.get('debugtoolbar.enabled', False):
278 if settings.get('debugtoolbar.enabled', False):
282 # if toolbar, then only http type exceptions get caught and rendered
279 # if toolbar, then only http type exceptions get caught and rendered
283 ExcClass = HTTPError
280 ExcClass = HTTPError
284 else:
281 else:
285 # if no toolbar, then any exception gets caught and rendered
282 # if no toolbar, then any exception gets caught and rendered
286 ExcClass = Exception
283 ExcClass = Exception
287 config.add_view(error_handler, context=ExcClass)
284 config.add_view(error_handler, context=ExcClass)
288
285
289
286
290 def includeme_last(config):
287 def includeme_last(config):
291 """
288 """
292 The static file catchall needs to be last in the view configuration.
289 The static file catchall needs to be last in the view configuration.
293 """
290 """
294 settings = config.registry.settings
291 settings = config.registry.settings
295
292
296 # Note: johbo: I would prefer to register a prefix for static files at some
293 # Note: johbo: I would prefer to register a prefix for static files at some
297 # point, e.g. move them under '_static/'. This would fully avoid that we
294 # point, e.g. move them under '_static/'. This would fully avoid that we
298 # can have name clashes with a repository name. Imaging someone calling his
295 # can have name clashes with a repository name. Imaging someone calling his
299 # repo "css" ;-) Also having an external web server to serve out the static
296 # repo "css" ;-) Also having an external web server to serve out the static
300 # files seems to be easier to set up if they have a common prefix.
297 # files seems to be easier to set up if they have a common prefix.
301 #
298 #
302 # Example: config.add_static_view('_static', path='rhodecode:public')
299 # Example: config.add_static_view('_static', path='rhodecode:public')
303 #
300 #
304 # It might be an option to register both paths for a while and then migrate
301 # It might be an option to register both paths for a while and then migrate
305 # over to the new location.
302 # over to the new location.
306
303
307 # Serving static files with a catchall.
304 # Serving static files with a catchall.
308 if settings['static_files']:
305 if settings['static_files']:
309 config.add_route('catchall_static', '/*subpath')
306 config.add_route('catchall_static', '/*subpath')
310 config.add_view(
307 config.add_view(
311 static_view('rhodecode:public'), route_name='catchall_static')
308 static_view('rhodecode:public'), route_name='catchall_static')
312
309
313
310
314 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
311 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
315 """
312 """
316 Apply outer WSGI middlewares around the application.
313 Apply outer WSGI middlewares around the application.
317
314
318 Part of this has been moved up from the Pylons layer, so that the
315 Part of this has been moved up from the Pylons layer, so that the
319 data is also available if old Pylons code is hit through an already ported
316 data is also available if old Pylons code is hit through an already ported
320 view.
317 view.
321 """
318 """
322 settings = config.registry.settings
319 settings = config.registry.settings
323
320
321 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
322 pyramid_app = HttpsFixup(pyramid_app, settings)
323
324 # Add RoutesMiddleware to support the pylons compatibility tween during
324 # Add RoutesMiddleware to support the pylons compatibility tween during
325
325 # migration to pyramid.
326 # migration to pyramid.
326 pyramid_app = RoutesMiddleware(
327 pyramid_app = RoutesMiddleware(
327 pyramid_app, config.registry._pylons_compat_config['routes.map'])
328 pyramid_app, config.registry._pylons_compat_config['routes.map'])
328
329
329 if asbool(settings.get('appenlight', 'false')):
330 if asbool(settings.get('appenlight', 'false')):
330 pyramid_app, _ = wrap_in_appenlight_if_enabled(
331 pyramid_app, _ = wrap_in_appenlight_if_enabled(
331 pyramid_app, config.registry._pylons_compat_config)
332 pyramid_app, config.registry._pylons_compat_config)
332
333
333 # TODO: johbo: Don't really see why we enable the gzip middleware when
334 # TODO: johbo: Don't really see why we enable the gzip middleware when
334 # serving static files, might be something that should have its own setting
335 # serving static files, might be something that should have its own setting
335 # as well?
336 # as well?
336 if settings['static_files']:
337 if settings['static_files']:
337 pyramid_app = make_gzip_middleware(
338 pyramid_app = make_gzip_middleware(
338 pyramid_app, settings, compress_level=1)
339 pyramid_app, settings, compress_level=1)
339
340
340 return pyramid_app
341 return pyramid_app
341
342
342
343
343 def sanitize_settings_and_apply_defaults(settings):
344 def sanitize_settings_and_apply_defaults(settings):
344 """
345 """
345 Applies settings defaults and does all type conversion.
346 Applies settings defaults and does all type conversion.
346
347
347 We would move all settings parsing and preparation into this place, so that
348 We would move all settings parsing and preparation into this place, so that
348 we have only one place left which deals with this part. The remaining parts
349 we have only one place left which deals with this part. The remaining parts
349 of the application would start to rely fully on well prepared settings.
350 of the application would start to rely fully on well prepared settings.
350
351
351 This piece would later be split up per topic to avoid a big fat monster
352 This piece would later be split up per topic to avoid a big fat monster
352 function.
353 function.
353 """
354 """
354
355
355 # Pyramid's mako renderer has to search in the templates folder so that the
356 # Pyramid's mako renderer has to search in the templates folder so that the
356 # old templates still work. Ported and new templates are expected to use
357 # old templates still work. Ported and new templates are expected to use
357 # real asset specifications for the includes.
358 # real asset specifications for the includes.
358 mako_directories = settings.setdefault('mako.directories', [
359 mako_directories = settings.setdefault('mako.directories', [
359 # Base templates of the original Pylons application
360 # Base templates of the original Pylons application
360 'rhodecode:templates',
361 'rhodecode:templates',
361 ])
362 ])
362 log.debug(
363 log.debug(
363 "Using the following Mako template directories: %s",
364 "Using the following Mako template directories: %s",
364 mako_directories)
365 mako_directories)
365
366
366 # Default includes, possible to change as a user
367 # Default includes, possible to change as a user
367 pyramid_includes = settings.setdefault('pyramid.includes', [
368 pyramid_includes = settings.setdefault('pyramid.includes', [
368 'rhodecode.lib.middleware.request_wrapper',
369 'rhodecode.lib.middleware.request_wrapper',
369 ])
370 ])
370 log.debug(
371 log.debug(
371 "Using the following pyramid.includes: %s",
372 "Using the following pyramid.includes: %s",
372 pyramid_includes)
373 pyramid_includes)
373
374
374 # TODO: johbo: Re-think this, usually the call to config.include
375 # TODO: johbo: Re-think this, usually the call to config.include
375 # should allow to pass in a prefix.
376 # should allow to pass in a prefix.
376 settings.setdefault('rhodecode.api.url', '/_admin/api')
377 settings.setdefault('rhodecode.api.url', '/_admin/api')
377
378
378 _bool_setting(settings, 'vcs.server.enable', 'true')
379 _bool_setting(settings, 'vcs.server.enable', 'true')
379 _bool_setting(settings, 'static_files', 'true')
380 _bool_setting(settings, 'static_files', 'true')
380 _bool_setting(settings, 'is_test', 'false')
381 _bool_setting(settings, 'is_test', 'false')
381
382
382 return settings
383 return settings
383
384
384
385
385 def _bool_setting(settings, name, default):
386 def _bool_setting(settings, name, default):
386 settings[name] = asbool(settings.get(name, default))
387 settings[name] = asbool(settings.get(name, default))
@@ -1,339 +1,340 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import formencode
22 import formencode
23 import logging
23 import logging
24 import urlparse
24 import urlparse
25
25
26 from pylons import url
26 from pylons import url
27 from pyramid.httpexceptions import HTTPFound
27 from pyramid.httpexceptions import HTTPFound
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from recaptcha.client.captcha import submit
29 from recaptcha.client.captcha import submit
30
30
31 from rhodecode.authentication.base import authenticate, HTTP_TYPE
31 from rhodecode.authentication.base import authenticate, HTTP_TYPE
32 from rhodecode.events import UserRegistered
32 from rhodecode.events import UserRegistered
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
34 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
35 from rhodecode.lib.base import get_ip_addr
35 from rhodecode.lib.base import get_ip_addr
36 from rhodecode.lib.exceptions import UserCreationError
36 from rhodecode.lib.exceptions import UserCreationError
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 from rhodecode.model.login_session import LoginSession
40 from rhodecode.model.login_session import LoginSession
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.settings import SettingsModel
43 from rhodecode.model.user import UserModel
43 from rhodecode.model.user import UserModel
44 from rhodecode.translation import _
44 from rhodecode.translation import _
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 def _store_user_in_session(session, username, remember=False):
50 def _store_user_in_session(session, username, remember=False):
51 user = User.get_by_username(username, case_insensitive=True)
51 user = User.get_by_username(username, case_insensitive=True)
52 auth_user = AuthUser(user.user_id)
52 auth_user = AuthUser(user.user_id)
53 auth_user.set_authenticated()
53 auth_user.set_authenticated()
54 cs = auth_user.get_cookie_store()
54 cs = auth_user.get_cookie_store()
55 session['rhodecode_user'] = cs
55 session['rhodecode_user'] = cs
56 user.update_lastlogin()
56 user.update_lastlogin()
57 Session().commit()
57 Session().commit()
58
58
59 # If they want to be remembered, update the cookie
59 # If they want to be remembered, update the cookie
60 if remember:
60 if remember:
61 _year = (datetime.datetime.now() +
61 _year = (datetime.datetime.now() +
62 datetime.timedelta(seconds=60 * 60 * 24 * 365))
62 datetime.timedelta(seconds=60 * 60 * 24 * 365))
63 session._set_cookie_expires(_year)
63 session._set_cookie_expires(_year)
64
64
65 session.save()
65 session.save()
66
66
67 log.info('user %s is now authenticated and stored in '
67 log.info('user %s is now authenticated and stored in '
68 'session, session attrs %s', username, cs)
68 'session, session attrs %s', username, cs)
69
69
70 # dumps session attrs back to cookie
70 # dumps session attrs back to cookie
71 session._update_cookie_out()
71 session._update_cookie_out()
72 # we set new cookie
72 # we set new cookie
73 headers = None
73 headers = None
74 if session.request['set_cookie']:
74 if session.request['set_cookie']:
75 # send set-cookie headers back to response to update cookie
75 # send set-cookie headers back to response to update cookie
76 headers = [('Set-Cookie', session.request['cookie_out'])]
76 headers = [('Set-Cookie', session.request['cookie_out'])]
77 return headers
77 return headers
78
78
79
79
80 def get_came_from(request):
80 def get_came_from(request):
81 came_from = safe_str(request.GET.get('came_from', ''))
81 came_from = safe_str(request.GET.get('came_from', ''))
82 parsed = urlparse.urlparse(came_from)
82 parsed = urlparse.urlparse(came_from)
83 allowed_schemes = ['http', 'https']
83 allowed_schemes = ['http', 'https']
84 if parsed.scheme and parsed.scheme not in allowed_schemes:
84 if parsed.scheme and parsed.scheme not in allowed_schemes:
85 log.error('Suspicious URL scheme detected %s for url %s' %
85 log.error('Suspicious URL scheme detected %s for url %s' %
86 (parsed.scheme, parsed))
86 (parsed.scheme, parsed))
87 came_from = url('home')
87 came_from = url('home')
88 elif parsed.netloc and request.host != parsed.netloc:
88 elif parsed.netloc and request.host != parsed.netloc:
89 log.error('Suspicious NETLOC detected %s for url %s server url '
89 log.error('Suspicious NETLOC detected %s for url %s server url '
90 'is: %s' % (parsed.netloc, parsed, request.host))
90 'is: %s' % (parsed.netloc, parsed, request.host))
91 came_from = url('home')
91 came_from = url('home')
92 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
92 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
93 log.error('Header injection detected `%s` for url %s server url ' %
93 log.error('Header injection detected `%s` for url %s server url ' %
94 (parsed.path, parsed))
94 (parsed.path, parsed))
95 came_from = url('home')
95 came_from = url('home')
96
96
97 return came_from or url('home')
97 return came_from or url('home')
98
98
99
99
100 class LoginView(object):
100 class LoginView(object):
101
101
102 def __init__(self, context, request):
102 def __init__(self, context, request):
103 self.request = request
103 self.request = request
104 self.context = context
104 self.context = context
105 self.session = request.session
105 self.session = request.session
106 self._rhodecode_user = request.user
106 self._rhodecode_user = request.user
107
107
108 def _get_template_context(self):
108 def _get_template_context(self):
109 return {
109 return {
110 'came_from': get_came_from(self.request),
110 'came_from': get_came_from(self.request),
111 'defaults': {},
111 'defaults': {},
112 'errors': {},
112 'errors': {},
113 }
113 }
114
114
115 @view_config(
115 @view_config(
116 route_name='login', request_method='GET',
116 route_name='login', request_method='GET',
117 renderer='rhodecode:templates/login.html')
117 renderer='rhodecode:templates/login.html')
118 def login(self):
118 def login(self):
119 came_from = get_came_from(self.request)
119 came_from = get_came_from(self.request)
120 user = self.request.user
120 user = self.request.user
121
121
122 # redirect if already logged in
122 # redirect if already logged in
123 if user.is_authenticated and not user.is_default and user.ip_allowed:
123 if user.is_authenticated and not user.is_default and user.ip_allowed:
124 raise HTTPFound(came_from)
124 raise HTTPFound(came_from)
125
125
126 # check if we use headers plugin, and try to login using it.
126 # check if we use headers plugin, and try to login using it.
127 try:
127 try:
128 log.debug('Running PRE-AUTH for headers based authentication')
128 log.debug('Running PRE-AUTH for headers based authentication')
129 auth_info = authenticate(
129 auth_info = authenticate(
130 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
130 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
131 if auth_info:
131 if auth_info:
132 headers = _store_user_in_session(
132 headers = _store_user_in_session(
133 self.session, auth_info.get('username'))
133 self.session, auth_info.get('username'))
134 raise HTTPFound(came_from, headers=headers)
134 raise HTTPFound(came_from, headers=headers)
135 except UserCreationError as e:
135 except UserCreationError as e:
136 log.error(e)
136 log.error(e)
137 self.session.flash(e, queue='error')
137 self.session.flash(e, queue='error')
138
138
139 return self._get_template_context()
139 return self._get_template_context()
140
140
141 @view_config(
141 @view_config(
142 route_name='login', request_method='POST',
142 route_name='login', request_method='POST',
143 renderer='rhodecode:templates/login.html')
143 renderer='rhodecode:templates/login.html')
144 def login_post(self):
144 def login_post(self):
145 came_from = get_came_from(self.request)
145 came_from = get_came_from(self.request)
146 session = self.request.session
146 session = self.request.session
147 login_form = LoginForm()()
147 login_form = LoginForm()()
148
148
149 try:
149 try:
150 session.invalidate()
150 session.invalidate()
151 form_result = login_form.to_python(self.request.params)
151 form_result = login_form.to_python(self.request.params)
152 # form checks for username/password, now we're authenticated
152 # form checks for username/password, now we're authenticated
153 headers = _store_user_in_session(
153 headers = _store_user_in_session(
154 self.session,
154 self.session,
155 username=form_result['username'],
155 username=form_result['username'],
156 remember=form_result['remember'])
156 remember=form_result['remember'])
157 log.debug('Redirecting to "%s" after login.', came_from)
157 raise HTTPFound(came_from, headers=headers)
158 raise HTTPFound(came_from, headers=headers)
158 except formencode.Invalid as errors:
159 except formencode.Invalid as errors:
159 defaults = errors.value
160 defaults = errors.value
160 # remove password from filling in form again
161 # remove password from filling in form again
161 del defaults['password']
162 del defaults['password']
162 render_ctx = self._get_template_context()
163 render_ctx = self._get_template_context()
163 render_ctx.update({
164 render_ctx.update({
164 'errors': errors.error_dict,
165 'errors': errors.error_dict,
165 'defaults': defaults,
166 'defaults': defaults,
166 })
167 })
167 return render_ctx
168 return render_ctx
168
169
169 except UserCreationError as e:
170 except UserCreationError as e:
170 # headers auth or other auth functions that create users on
171 # headers auth or other auth functions that create users on
171 # the fly can throw this exception signaling that there's issue
172 # the fly can throw this exception signaling that there's issue
172 # with user creation, explanation should be provided in
173 # with user creation, explanation should be provided in
173 # Exception itself
174 # Exception itself
174 session.flash(e, queue='error')
175 session.flash(e, queue='error')
175 return self._get_template_context()
176 return self._get_template_context()
176
177
177 @CSRFRequired()
178 @CSRFRequired()
178 @view_config(route_name='logout', request_method='POST')
179 @view_config(route_name='logout', request_method='POST')
179 def logout(self):
180 def logout(self):
180 LoginSession().destroy_user_session()
181 LoginSession().destroy_user_session()
181 return HTTPFound(url('home'))
182 return HTTPFound(url('home'))
182
183
183 @HasPermissionAnyDecorator(
184 @HasPermissionAnyDecorator(
184 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
185 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
185 @view_config(
186 @view_config(
186 route_name='register', request_method='GET',
187 route_name='register', request_method='GET',
187 renderer='rhodecode:templates/register.html',)
188 renderer='rhodecode:templates/register.html',)
188 def register(self, defaults=None, errors=None):
189 def register(self, defaults=None, errors=None):
189 defaults = defaults or {}
190 defaults = defaults or {}
190 errors = errors or {}
191 errors = errors or {}
191
192
192 settings = SettingsModel().get_all_settings()
193 settings = SettingsModel().get_all_settings()
193 captcha_public_key = settings.get('rhodecode_captcha_public_key')
194 captcha_public_key = settings.get('rhodecode_captcha_public_key')
194 captcha_private_key = settings.get('rhodecode_captcha_private_key')
195 captcha_private_key = settings.get('rhodecode_captcha_private_key')
195 captcha_active = bool(captcha_private_key)
196 captcha_active = bool(captcha_private_key)
196 register_message = settings.get('rhodecode_register_message') or ''
197 register_message = settings.get('rhodecode_register_message') or ''
197 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
198 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
198 .AuthUser.permissions['global']
199 .AuthUser.permissions['global']
199
200
200 render_ctx = self._get_template_context()
201 render_ctx = self._get_template_context()
201 render_ctx.update({
202 render_ctx.update({
202 'defaults': defaults,
203 'defaults': defaults,
203 'errors': errors,
204 'errors': errors,
204 'auto_active': auto_active,
205 'auto_active': auto_active,
205 'captcha_active': captcha_active,
206 'captcha_active': captcha_active,
206 'captcha_public_key': captcha_public_key,
207 'captcha_public_key': captcha_public_key,
207 'register_message': register_message,
208 'register_message': register_message,
208 })
209 })
209 return render_ctx
210 return render_ctx
210
211
211 @HasPermissionAnyDecorator(
212 @HasPermissionAnyDecorator(
212 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
213 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
213 @view_config(
214 @view_config(
214 route_name='register', request_method='POST',
215 route_name='register', request_method='POST',
215 renderer='rhodecode:templates/register.html')
216 renderer='rhodecode:templates/register.html')
216 def register_post(self):
217 def register_post(self):
217 captcha_private_key = SettingsModel().get_setting_by_name(
218 captcha_private_key = SettingsModel().get_setting_by_name(
218 'rhodecode_captcha_private_key')
219 'rhodecode_captcha_private_key')
219 captcha_active = bool(captcha_private_key)
220 captcha_active = bool(captcha_private_key)
220 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
221 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
221 .AuthUser.permissions['global']
222 .AuthUser.permissions['global']
222
223
223 register_form = RegisterForm()()
224 register_form = RegisterForm()()
224 try:
225 try:
225 form_result = register_form.to_python(self.request.params)
226 form_result = register_form.to_python(self.request.params)
226 form_result['active'] = auto_active
227 form_result['active'] = auto_active
227
228
228 if captcha_active:
229 if captcha_active:
229 response = submit(
230 response = submit(
230 self.request.params.get('recaptcha_challenge_field'),
231 self.request.params.get('recaptcha_challenge_field'),
231 self.request.params.get('recaptcha_response_field'),
232 self.request.params.get('recaptcha_response_field'),
232 private_key=captcha_private_key,
233 private_key=captcha_private_key,
233 remoteip=get_ip_addr(self.request.environ))
234 remoteip=get_ip_addr(self.request.environ))
234 if captcha_active and not response.is_valid:
235 if captcha_active and not response.is_valid:
235 _value = form_result
236 _value = form_result
236 _msg = _('bad captcha')
237 _msg = _('bad captcha')
237 error_dict = {'recaptcha_field': _msg}
238 error_dict = {'recaptcha_field': _msg}
238 raise formencode.Invalid(_msg, _value, None,
239 raise formencode.Invalid(_msg, _value, None,
239 error_dict=error_dict)
240 error_dict=error_dict)
240
241
241 new_user = UserModel().create_registration(form_result)
242 new_user = UserModel().create_registration(form_result)
242 event = UserRegistered(user=new_user, session=self.session)
243 event = UserRegistered(user=new_user, session=self.session)
243 self.request.registry.notify(event)
244 self.request.registry.notify(event)
244 self.session.flash(
245 self.session.flash(
245 _('You have successfully registered with RhodeCode'),
246 _('You have successfully registered with RhodeCode'),
246 queue='success')
247 queue='success')
247 Session().commit()
248 Session().commit()
248
249
249 redirect_ro = self.request.route_path('login')
250 redirect_ro = self.request.route_path('login')
250 raise HTTPFound(redirect_ro)
251 raise HTTPFound(redirect_ro)
251
252
252 except formencode.Invalid as errors:
253 except formencode.Invalid as errors:
253 del errors.value['password']
254 del errors.value['password']
254 del errors.value['password_confirmation']
255 del errors.value['password_confirmation']
255 return self.register(
256 return self.register(
256 defaults=errors.value, errors=errors.error_dict)
257 defaults=errors.value, errors=errors.error_dict)
257
258
258 except UserCreationError as e:
259 except UserCreationError as e:
259 # container auth or other auth functions that create users on
260 # container auth or other auth functions that create users on
260 # the fly can throw this exception signaling that there's issue
261 # the fly can throw this exception signaling that there's issue
261 # with user creation, explanation should be provided in
262 # with user creation, explanation should be provided in
262 # Exception itself
263 # Exception itself
263 self.session.flash(e, queue='error')
264 self.session.flash(e, queue='error')
264 return self.register()
265 return self.register()
265
266
266 @view_config(
267 @view_config(
267 route_name='reset_password', request_method=('GET', 'POST'),
268 route_name='reset_password', request_method=('GET', 'POST'),
268 renderer='rhodecode:templates/password_reset.html')
269 renderer='rhodecode:templates/password_reset.html')
269 def password_reset(self):
270 def password_reset(self):
270 settings = SettingsModel().get_all_settings()
271 settings = SettingsModel().get_all_settings()
271 captcha_private_key = settings.get('rhodecode_captcha_private_key')
272 captcha_private_key = settings.get('rhodecode_captcha_private_key')
272 captcha_active = bool(captcha_private_key)
273 captcha_active = bool(captcha_private_key)
273 captcha_public_key = settings.get('rhodecode_captcha_public_key')
274 captcha_public_key = settings.get('rhodecode_captcha_public_key')
274
275
275 render_ctx = {
276 render_ctx = {
276 'captcha_active': captcha_active,
277 'captcha_active': captcha_active,
277 'captcha_public_key': captcha_public_key,
278 'captcha_public_key': captcha_public_key,
278 'defaults': {},
279 'defaults': {},
279 'errors': {},
280 'errors': {},
280 }
281 }
281
282
282 if self.request.POST:
283 if self.request.POST:
283 password_reset_form = PasswordResetForm()()
284 password_reset_form = PasswordResetForm()()
284 try:
285 try:
285 form_result = password_reset_form.to_python(
286 form_result = password_reset_form.to_python(
286 self.request.params)
287 self.request.params)
287 if captcha_active:
288 if captcha_active:
288 response = submit(
289 response = submit(
289 self.request.params.get('recaptcha_challenge_field'),
290 self.request.params.get('recaptcha_challenge_field'),
290 self.request.params.get('recaptcha_response_field'),
291 self.request.params.get('recaptcha_response_field'),
291 private_key=captcha_private_key,
292 private_key=captcha_private_key,
292 remoteip=get_ip_addr(self.request.environ))
293 remoteip=get_ip_addr(self.request.environ))
293 if captcha_active and not response.is_valid:
294 if captcha_active and not response.is_valid:
294 _value = form_result
295 _value = form_result
295 _msg = _('bad captcha')
296 _msg = _('bad captcha')
296 error_dict = {'recaptcha_field': _msg}
297 error_dict = {'recaptcha_field': _msg}
297 raise formencode.Invalid(_msg, _value, None,
298 raise formencode.Invalid(_msg, _value, None,
298 error_dict=error_dict)
299 error_dict=error_dict)
299
300
300 # Generate reset URL and send mail.
301 # Generate reset URL and send mail.
301 user_email = form_result['email']
302 user_email = form_result['email']
302 user = User.get_by_email(user_email)
303 user = User.get_by_email(user_email)
303 password_reset_url = self.request.route_url(
304 password_reset_url = self.request.route_url(
304 'reset_password_confirmation',
305 'reset_password_confirmation',
305 _query={'key': user.api_key})
306 _query={'key': user.api_key})
306 UserModel().reset_password_link(
307 UserModel().reset_password_link(
307 form_result, password_reset_url)
308 form_result, password_reset_url)
308
309
309 # Display success message and redirect.
310 # Display success message and redirect.
310 self.session.flash(
311 self.session.flash(
311 _('Your password reset link was sent'),
312 _('Your password reset link was sent'),
312 queue='success')
313 queue='success')
313 return HTTPFound(self.request.route_path('login'))
314 return HTTPFound(self.request.route_path('login'))
314
315
315 except formencode.Invalid as errors:
316 except formencode.Invalid as errors:
316 render_ctx.update({
317 render_ctx.update({
317 'defaults': errors.value,
318 'defaults': errors.value,
318 'errors': errors.error_dict,
319 'errors': errors.error_dict,
319 })
320 })
320
321
321 return render_ctx
322 return render_ctx
322
323
323 @view_config(route_name='reset_password_confirmation',
324 @view_config(route_name='reset_password_confirmation',
324 request_method='GET')
325 request_method='GET')
325 def password_reset_confirmation(self):
326 def password_reset_confirmation(self):
326 if self.request.GET and self.request.GET.get('key'):
327 if self.request.GET and self.request.GET.get('key'):
327 try:
328 try:
328 user = User.get_by_auth_token(self.request.GET.get('key'))
329 user = User.get_by_auth_token(self.request.GET.get('key'))
329 data = {'email': user.email}
330 data = {'email': user.email}
330 UserModel().reset_password(data)
331 UserModel().reset_password(data)
331 self.session.flash(
332 self.session.flash(
332 _('Your password reset was successful, '
333 _('Your password reset was successful, '
333 'a new password has been sent to your email'),
334 'a new password has been sent to your email'),
334 queue='success')
335 queue='success')
335 except Exception as e:
336 except Exception as e:
336 log.error(e)
337 log.error(e)
337 return HTTPFound(self.request.route_path('reset_password'))
338 return HTTPFound(self.request.route_path('reset_password'))
338
339
339 return HTTPFound(self.request.route_path('login'))
340 return HTTPFound(self.request.route_path('login'))
General Comments 0
You need to be logged in to leave comments. Login now