We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Login: Malformed url redirecting to external login
## BUG/PROBLEM REPORT

### What I did:

On a new Plone site (Plone 5.2rc2-pending) with an external login addon (pas.plugins.authomatic), the login redirection uses a malformed url.

This issue happens if no **next** parameter is provided in the redirect but a **came_from** parameter is. [Code producing the error](https://github.com/plone/Products.CMFPlone/blob/master/Products/CMFPlone/browser/login/login.py#L68)

### What I expect to happen:
Redirection to **http://localhost:8080/Plone/authomatic-handler?came_from=http://localhost:8080/Plone/**

### What actually happened:
Redirected to **http://localhost:8080/Plone/authomatic-handler&came_from=http://localhost:8080/Plone/**

### What version of Plone/ Addons I am using:
Plone 5.2-rc2-pending
pas.plugin.authomatic 1.0b2.dev0


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Products/CMFPlone/browser/login/login.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 from DateTime import DateTime
3 from plone.app.users.browser.passwordpanel import PasswordPanel
4 from plone.registry.interfaces import IRegistry
5 from Products.CMFCore.utils import getToolByName
6 from Products.CMFPlone import PloneMessageFactory as _
7 from Products.CMFPlone.interfaces import IForcePasswordChange
8 from Products.CMFPlone.interfaces import IInitialLogin
9 from Products.CMFPlone.interfaces import ILoginForm
10 from Products.CMFPlone.interfaces import ILoginFormSchema
11 from Products.CMFPlone.interfaces import IRedirectAfterLogin
12 from Products.CMFPlone.interfaces import ISecuritySchema
13 from Products.Five.browser import BrowserView
14 from Products.statusmessages.interfaces import IStatusMessage
15 from six.moves.urllib import parse
16 from z3c.form import button
17 from z3c.form import field
18 from z3c.form import form
19 from z3c.form.interfaces import HIDDEN_MODE
20 from zope.component import getMultiAdapter
21 from zope.component import queryMultiAdapter
22 from zope.component import queryUtility
23 from zope.interface import implementer
24 
25 
26 # TODO: Scale down this list now that we've removed a lot of
27 # templates.
28 LOGIN_TEMPLATE_IDS = [
29     'localhost',
30     'logged-out',
31     'logged_in',
32     'login',
33     'login_failed',
34     'login_form',
35     'login_password',
36     'login_success',
37     'logout',
38     'mail_password',
39     'mail_password_form',
40     'member_search_results',
41     'pwreset_finish',
42     'passwordreset',
43     'register',
44     'registered',
45     'require_login',
46 ]
47 
48 
49 @implementer(ILoginForm)
50 class LoginForm(form.EditForm):
51     """ Implementation of the login form """
52 
53     fields = field.Fields(ILoginFormSchema)
54 
55     id = 'LoginForm'
56     label = _('label_log_in', default=u'Log in')
57 
58     ignoreContext = True
59     prefix = ''
60 
61     def render(self):
62         registry = queryUtility(IRegistry)
63         ext_login_url = registry['plone.external_login_url']
64         if ext_login_url:
65             return self._handle_external_login(ext_login_url)
66         return self.index()
67 
68     def _handle_external_login(self, url):
69         """Handle login on this portal where login is external."""
70         next_ = self.request.get('next', None)
71         portal_url = getToolByName(self.context, 'portal_url')
72         if next_ is not None and not portal_url.isURLInPortal(next_):
73             next_ = None
74         if next_ is not None:
75             url = '{0}?next={1}'.format(url, next_)
76         came_from = self.request.get('came_from')
77         if came_from:
78             url = '{0}&came_from={1}'.format(url, came_from)
79         self.request.response.redirect(url)
80 
81     def _get_auth(self):
82         try:
83             return self.context.acl_users.credentials_cookie_auth
84         except AttributeError:
85             try:
86                 return self.context.cookie_authentication
87             except AttributeError:
88                 pass
89 
90     def updateWidgets(self):
91         auth = self._get_auth()
92 
93         if auth:
94             fieldname_name = auth.get('name_cookie', '__ac_name')
95             fieldname_password = auth.get('pw_cookie', '__ac_password')
96         else:
97             fieldname_name = '__ac_name'
98             fieldname_password = '__ac_password'
99 
100         self.fields['ac_name'].__name__ = fieldname_name
101         self.fields['ac_password'].__name__ = fieldname_password
102 
103         super(LoginForm, self).updateWidgets(prefix='')
104 
105         if self.use_email_as_login():
106             self.widgets[fieldname_name].label = _(u'label_email',
107                                                    default=u'Email')
108         self.widgets['came_from'].mode = HIDDEN_MODE
109         self.widgets['came_from'].value = self.get_came_from()
110 
111     def get_came_from(self):
112         came_from = self.request.get('came_from', None)
113         if not came_from:
114             came_from = self.request.get('HTTP_REFERER', None)
115             if not came_from:
116                 return
117         url_tool = getToolByName(self.context, 'portal_url')
118         if not url_tool.isURLInPortal(came_from):
119             return
120         came_from_path = parse.urlparse(came_from)[2].split('/')
121         for login_template_id in LOGIN_TEMPLATE_IDS:
122             if login_template_id in came_from_path:
123                 return
124         return came_from
125 
126     def updateActions(self):
127         super(LoginForm, self).updateActions()
128         self.actions['login'].addClass('context')
129 
130     def _post_login(self):
131         membership_tool = getToolByName(self.context, 'portal_membership')
132         member = membership_tool.getAuthenticatedMember()
133         must_change_password = member.getProperty('must_change_password', 0)
134         login_time = member.getProperty('login_time', '2000/01/01')
135         if not isinstance(login_time, DateTime):
136             login_time = DateTime(login_time)
137         is_initial_login = login_time == DateTime('2000/01/01')
138 
139         membership_tool.loginUser(self.request)
140         if is_initial_login:
141             self.handle_initial_login()
142 
143         if must_change_password:
144             self.force_password_change()
145         return is_initial_login
146 
147     @button.buttonAndHandler(_('Log in'), name='login')
148     def handleLogin(self, action):
149         data, errors = self.extractData()
150         if errors:
151             self.status = self.formErrorsMessage
152             return
153         membership_tool = getToolByName(self.context, 'portal_membership')
154         status_msg = IStatusMessage(self.request)
155         if membership_tool.isAnonymousUser():
156             self.request.response.expireCookie('__ac', path='/')
157             if self.use_email_as_login():
158                 status_msg.addStatusMessage(
159                     _(
160                         u'Login failed. Both email address and password are '
161                         u'case sensitive, check that caps lock is not enabled.'
162                     ),
163                     'error',
164                 )
165             else:
166                 status_msg.addStatusMessage(
167                     _(
168                         u'Login failed. Both login name and password are case '
169                         u'sensitive, check that caps lock is not enabled.'
170                     ),
171                     'error',
172                 )
173             return
174 
175         is_initial_login = self._post_login()
176         status_msg.addStatusMessage(
177             _(
178                 u'you_are_now_logged_in',
179                 default=u'Welcome! You are now logged in.',
180             ),
181             'info'
182         )
183 
184         came_from = data.get('came_from', None)
185         self.redirect_after_login(came_from, is_initial_login)
186 
187     def handle_initial_login(self):
188         handler = queryMultiAdapter(
189             (self.context, self.request),
190             IInitialLogin,
191         )
192         if handler:
193             handler()
194 
195     def force_password_change(self):
196         handler = queryMultiAdapter(
197             (self.context, self.request),
198             IForcePasswordChange,
199         )
200         if handler:
201             handler()
202 
203     def redirect_after_login(self, came_from=None, is_initial_login=False):
204         adapter = queryMultiAdapter(
205             (self.context, self.request),
206             IRedirectAfterLogin
207         )
208         if adapter:
209             came_from = adapter(came_from, is_initial_login)
210         if not came_from:
211             came_from = self.context.absolute_url()
212 
213         self.request.response.redirect(came_from)
214 
215     def self_registration_enabled(self):
216         registry = queryUtility(IRegistry)
217         security_settings = registry.forInterface(
218             ISecuritySchema,
219             prefix='plone'
220         )
221         return security_settings.enable_self_reg
222 
223     def use_email_as_login(self):
224         registry = queryUtility(IRegistry)
225         security_settings = registry.forInterface(
226             ISecuritySchema,
227             prefix='plone'
228         )
229         return security_settings.use_email_as_login
230 
231 
232 class FailsafeLoginForm(LoginForm):
233 
234     def render(self):
235         return self.index()
236 
237 
238 class RequireLoginView(BrowserView):
239 
240     def __call__(self):
241         portal_state = getMultiAdapter(
242             (self.context, self.request),
243             name='plone_portal_state',
244         )
245         portal = portal_state.portal()
246         if portal_state.anonymous():
247             url = '{0:s}/login'.format(portal.absolute_url())
248             came_from = self.request.get('came_from', None)
249             if came_from:
250                 url += '?came_from={0:s}'.format(parse.quote(came_from))
251         else:
252             url = '{0:s}/insufficient-privileges'.format(portal.absolute_url())
253 
254         self.request.response.redirect(url)
255 
256 
257 class InsufficientPrivilegesView(BrowserView):
258 
259     def request_url(self):
260         return self.request.get('came_from')
261 
262 
263 class InitialLoginPasswordChange(PasswordPanel):
264 
265     def render(self):
266         return self.index()
267 
268     @button.buttonAndHandler(
269         _(u'label_change_password', default=u'Change Password'),
270         name='reset_passwd',
271     )
272     def action_reset_passwd(self, action):
273         super(InitialLoginPasswordChange, self).action_reset_passwd(
274             self, action)
275         if not action.form.widgets.errors:
276             self.request.response.redirect(self.context.portal_url())
277 
278 
279 class ForcedPasswordChange(PasswordPanel):
280 
281     def render(self):
282         return self.index()
283 
284     @button.buttonAndHandler(
285         _(u'label_change_password', default=u'Change Password'),
286         name='reset_passwd',
287     )
288     def action_reset_passwd(self, action):
289         super(ForcedPasswordChange, self).action_reset_passwd(self, action)
290         if not action.form.widgets.errors:
291             membership_tool = getToolByName(self.context, 'portal_membership')
292             member = membership_tool.getAuthenticatedMember()
293             member.setProperties(must_change_password=0)
294             self.request.response.redirect(self.context.portal_url())
295
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Products/CMFPlone/browser/login/login.py b/Products/CMFPlone/browser/login/login.py
--- a/Products/CMFPlone/browser/login/login.py
+++ b/Products/CMFPlone/browser/login/login.py
@@ -68,15 +68,18 @@
     def _handle_external_login(self, url):
         """Handle login on this portal where login is external."""
         next_ = self.request.get('next', None)
+        came_from = self.request.get('came_from')
+        urlparts = [part for part in parse.urlparse(url)]
+        qs = dict(parse.parse_qsl(urlparts[4]))
         portal_url = getToolByName(self.context, 'portal_url')
         if next_ is not None and not portal_url.isURLInPortal(next_):
             next_ = None
         if next_ is not None:
-            url = '{0}?next={1}'.format(url, next_)
-        came_from = self.request.get('came_from')
+            qs['next'] = next_
         if came_from:
-            url = '{0}&came_from={1}'.format(url, came_from)
-        self.request.response.redirect(url)
+            qs['came_from'] = came_from
+        urlparts[4] = parse.urlencode(qs)
+        self.request.response.redirect(parse.urlunparse(urlparts))
 
     def _get_auth(self):
         try:

{"golden_diff": "diff --git a/Products/CMFPlone/browser/login/login.py b/Products/CMFPlone/browser/login/login.py\n--- a/Products/CMFPlone/browser/login/login.py\n+++ b/Products/CMFPlone/browser/login/login.py\n@@ -68,15 +68,18 @@\n     def _handle_external_login(self, url):\n         \"\"\"Handle login on this portal where login is external.\"\"\"\n         next_ = self.request.get('next', None)\n+        came_from = self.request.get('came_from')\n+        urlparts = [part for part in parse.urlparse(url)]\n+        qs = dict(parse.parse_qsl(urlparts[4]))\n         portal_url = getToolByName(self.context, 'portal_url')\n         if next_ is not None and not portal_url.isURLInPortal(next_):\n             next_ = None\n         if next_ is not None:\n-            url = '{0}?next={1}'.format(url, next_)\n-        came_from = self.request.get('came_from')\n+            qs['next'] = next_\n         if came_from:\n-            url = '{0}&came_from={1}'.format(url, came_from)\n-        self.request.response.redirect(url)\n+            qs['came_from'] = came_from\n+        urlparts[4] = parse.urlencode(qs)\n+        self.request.response.redirect(parse.urlunparse(urlparts))\n \n     def _get_auth(self):\n         try:\n", "issue": "Login: Malformed url redirecting to external login\n## BUG/PROBLEM REPORT\r\n\r\n### What I did:\r\n\r\nOn a new Plone site (Plone 5.2rc2-pending) with an external login addon (pas.plugins.authomatic), the login redirection uses a malformed url.\r\n\r\nThis issue happens if no **next** parameter is provided in the redirect but a **came_from** parameter is. [Code producing the error](https://github.com/plone/Products.CMFPlone/blob/master/Products/CMFPlone/browser/login/login.py#L68)\r\n\r\n### What I expect to happen:\r\nRedirection to **http://localhost:8080/Plone/authomatic-handler?came_from=http://localhost:8080/Plone/**\r\n\r\n### What actually happened:\r\nRedirected to **http://localhost:8080/Plone/authomatic-handler&came_from=http://localhost:8080/Plone/**\r\n\r\n### What version of Plone/ Addons I am using:\r\nPlone 5.2-rc2-pending\r\npas.plugin.authomatic 1.0b2.dev0\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nfrom DateTime import DateTime\nfrom plone.app.users.browser.passwordpanel import PasswordPanel\nfrom plone.registry.interfaces import IRegistry\nfrom Products.CMFCore.utils import getToolByName\nfrom Products.CMFPlone import PloneMessageFactory as _\nfrom Products.CMFPlone.interfaces import IForcePasswordChange\nfrom Products.CMFPlone.interfaces import IInitialLogin\nfrom Products.CMFPlone.interfaces import ILoginForm\nfrom Products.CMFPlone.interfaces import ILoginFormSchema\nfrom Products.CMFPlone.interfaces import IRedirectAfterLogin\nfrom Products.CMFPlone.interfaces import ISecuritySchema\nfrom Products.Five.browser import BrowserView\nfrom Products.statusmessages.interfaces import IStatusMessage\nfrom six.moves.urllib import parse\nfrom z3c.form import button\nfrom z3c.form import field\nfrom z3c.form import form\nfrom z3c.form.interfaces import HIDDEN_MODE\nfrom zope.component import getMultiAdapter\nfrom zope.component import queryMultiAdapter\nfrom zope.component import queryUtility\nfrom zope.interface import implementer\n\n\n# TODO: Scale down this list now that we've removed a lot of\n# templates.\nLOGIN_TEMPLATE_IDS = [\n    'localhost',\n    'logged-out',\n    'logged_in',\n    'login',\n    'login_failed',\n    'login_form',\n    'login_password',\n    'login_success',\n    'logout',\n    'mail_password',\n    'mail_password_form',\n    'member_search_results',\n    'pwreset_finish',\n    'passwordreset',\n    'register',\n    'registered',\n    'require_login',\n]\n\n\n@implementer(ILoginForm)\nclass LoginForm(form.EditForm):\n    \"\"\" Implementation of the login form \"\"\"\n\n    fields = field.Fields(ILoginFormSchema)\n\n    id = 'LoginForm'\n    label = _('label_log_in', default=u'Log in')\n\n    ignoreContext = True\n    prefix = ''\n\n    def render(self):\n        registry = queryUtility(IRegistry)\n        ext_login_url = registry['plone.external_login_url']\n        if ext_login_url:\n            return self._handle_external_login(ext_login_url)\n        return self.index()\n\n    def _handle_external_login(self, url):\n        \"\"\"Handle login on this portal where login is external.\"\"\"\n        next_ = self.request.get('next', None)\n        portal_url = getToolByName(self.context, 'portal_url')\n        if next_ is not None and not portal_url.isURLInPortal(next_):\n            next_ = None\n        if next_ is not None:\n            url = '{0}?next={1}'.format(url, next_)\n        came_from = self.request.get('came_from')\n        if came_from:\n            url = '{0}&came_from={1}'.format(url, came_from)\n        self.request.response.redirect(url)\n\n    def _get_auth(self):\n        try:\n            return self.context.acl_users.credentials_cookie_auth\n        except AttributeError:\n            try:\n                return self.context.cookie_authentication\n            except AttributeError:\n                pass\n\n    def updateWidgets(self):\n        auth = self._get_auth()\n\n        if auth:\n            fieldname_name = auth.get('name_cookie', '__ac_name')\n            fieldname_password = auth.get('pw_cookie', '__ac_password')\n        else:\n            fieldname_name = '__ac_name'\n            fieldname_password = '__ac_password'\n\n        self.fields['ac_name'].__name__ = fieldname_name\n        self.fields['ac_password'].__name__ = fieldname_password\n\n        super(LoginForm, self).updateWidgets(prefix='')\n\n        if self.use_email_as_login():\n            self.widgets[fieldname_name].label = _(u'label_email',\n                                                   default=u'Email')\n        self.widgets['came_from'].mode = HIDDEN_MODE\n        self.widgets['came_from'].value = self.get_came_from()\n\n    def get_came_from(self):\n        came_from = self.request.get('came_from', None)\n        if not came_from:\n            came_from = self.request.get('HTTP_REFERER', None)\n            if not came_from:\n                return\n        url_tool = getToolByName(self.context, 'portal_url')\n        if not url_tool.isURLInPortal(came_from):\n            return\n        came_from_path = parse.urlparse(came_from)[2].split('/')\n        for login_template_id in LOGIN_TEMPLATE_IDS:\n            if login_template_id in came_from_path:\n                return\n        return came_from\n\n    def updateActions(self):\n        super(LoginForm, self).updateActions()\n        self.actions['login'].addClass('context')\n\n    def _post_login(self):\n        membership_tool = getToolByName(self.context, 'portal_membership')\n        member = membership_tool.getAuthenticatedMember()\n        must_change_password = member.getProperty('must_change_password', 0)\n        login_time = member.getProperty('login_time', '2000/01/01')\n        if not isinstance(login_time, DateTime):\n            login_time = DateTime(login_time)\n        is_initial_login = login_time == DateTime('2000/01/01')\n\n        membership_tool.loginUser(self.request)\n        if is_initial_login:\n            self.handle_initial_login()\n\n        if must_change_password:\n            self.force_password_change()\n        return is_initial_login\n\n    @button.buttonAndHandler(_('Log in'), name='login')\n    def handleLogin(self, action):\n        data, errors = self.extractData()\n        if errors:\n            self.status = self.formErrorsMessage\n            return\n        membership_tool = getToolByName(self.context, 'portal_membership')\n        status_msg = IStatusMessage(self.request)\n        if membership_tool.isAnonymousUser():\n            self.request.response.expireCookie('__ac', path='/')\n            if self.use_email_as_login():\n                status_msg.addStatusMessage(\n                    _(\n                        u'Login failed. Both email address and password are '\n                        u'case sensitive, check that caps lock is not enabled.'\n                    ),\n                    'error',\n                )\n            else:\n                status_msg.addStatusMessage(\n                    _(\n                        u'Login failed. Both login name and password are case '\n                        u'sensitive, check that caps lock is not enabled.'\n                    ),\n                    'error',\n                )\n            return\n\n        is_initial_login = self._post_login()\n        status_msg.addStatusMessage(\n            _(\n                u'you_are_now_logged_in',\n                default=u'Welcome! You are now logged in.',\n            ),\n            'info'\n        )\n\n        came_from = data.get('came_from', None)\n        self.redirect_after_login(came_from, is_initial_login)\n\n    def handle_initial_login(self):\n        handler = queryMultiAdapter(\n            (self.context, self.request),\n            IInitialLogin,\n        )\n        if handler:\n            handler()\n\n    def force_password_change(self):\n        handler = queryMultiAdapter(\n            (self.context, self.request),\n            IForcePasswordChange,\n        )\n        if handler:\n            handler()\n\n    def redirect_after_login(self, came_from=None, is_initial_login=False):\n        adapter = queryMultiAdapter(\n            (self.context, self.request),\n            IRedirectAfterLogin\n        )\n        if adapter:\n            came_from = adapter(came_from, is_initial_login)\n        if not came_from:\n            came_from = self.context.absolute_url()\n\n        self.request.response.redirect(came_from)\n\n    def self_registration_enabled(self):\n        registry = queryUtility(IRegistry)\n        security_settings = registry.forInterface(\n            ISecuritySchema,\n            prefix='plone'\n        )\n        return security_settings.enable_self_reg\n\n    def use_email_as_login(self):\n        registry = queryUtility(IRegistry)\n        security_settings = registry.forInterface(\n            ISecuritySchema,\n            prefix='plone'\n        )\n        return security_settings.use_email_as_login\n\n\nclass FailsafeLoginForm(LoginForm):\n\n    def render(self):\n        return self.index()\n\n\nclass RequireLoginView(BrowserView):\n\n    def __call__(self):\n        portal_state = getMultiAdapter(\n            (self.context, self.request),\n            name='plone_portal_state',\n        )\n        portal = portal_state.portal()\n        if portal_state.anonymous():\n            url = '{0:s}/login'.format(portal.absolute_url())\n            came_from = self.request.get('came_from', None)\n            if came_from:\n                url += '?came_from={0:s}'.format(parse.quote(came_from))\n        else:\n            url = '{0:s}/insufficient-privileges'.format(portal.absolute_url())\n\n        self.request.response.redirect(url)\n\n\nclass InsufficientPrivilegesView(BrowserView):\n\n    def request_url(self):\n        return self.request.get('came_from')\n\n\nclass InitialLoginPasswordChange(PasswordPanel):\n\n    def render(self):\n        return self.index()\n\n    @button.buttonAndHandler(\n        _(u'label_change_password', default=u'Change Password'),\n        name='reset_passwd',\n    )\n    def action_reset_passwd(self, action):\n        super(InitialLoginPasswordChange, self).action_reset_passwd(\n            self, action)\n        if not action.form.widgets.errors:\n            self.request.response.redirect(self.context.portal_url())\n\n\nclass ForcedPasswordChange(PasswordPanel):\n\n    def render(self):\n        return self.index()\n\n    @button.buttonAndHandler(\n        _(u'label_change_password', default=u'Change Password'),\n        name='reset_passwd',\n    )\n    def action_reset_passwd(self, action):\n        super(ForcedPasswordChange, self).action_reset_passwd(self, action)\n        if not action.form.widgets.errors:\n            membership_tool = getToolByName(self.context, 'portal_membership')\n            member = membership_tool.getAuthenticatedMember()\n            member.setProperties(must_change_password=0)\n            self.request.response.redirect(self.context.portal_url())\n", "path": "Products/CMFPlone/browser/login/login.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nfrom DateTime import DateTime\nfrom plone.app.users.browser.passwordpanel import PasswordPanel\nfrom plone.registry.interfaces import IRegistry\nfrom Products.CMFCore.utils import getToolByName\nfrom Products.CMFPlone import PloneMessageFactory as _\nfrom Products.CMFPlone.interfaces import IForcePasswordChange\nfrom Products.CMFPlone.interfaces import IInitialLogin\nfrom Products.CMFPlone.interfaces import ILoginForm\nfrom Products.CMFPlone.interfaces import ILoginFormSchema\nfrom Products.CMFPlone.interfaces import IRedirectAfterLogin\nfrom Products.CMFPlone.interfaces import ISecuritySchema\nfrom Products.Five.browser import BrowserView\nfrom Products.statusmessages.interfaces import IStatusMessage\nfrom six.moves.urllib import parse\nfrom z3c.form import button\nfrom z3c.form import field\nfrom z3c.form import form\nfrom z3c.form.interfaces import HIDDEN_MODE\nfrom zope.component import getMultiAdapter\nfrom zope.component import queryMultiAdapter\nfrom zope.component import queryUtility\nfrom zope.interface import implementer\n\n\n# TODO: Scale down this list now that we've removed a lot of\n# templates.\nLOGIN_TEMPLATE_IDS = [\n    'localhost',\n    'logged-out',\n    'logged_in',\n    'login',\n    'login_failed',\n    'login_form',\n    'login_password',\n    'login_success',\n    'logout',\n    'mail_password',\n    'mail_password_form',\n    'member_search_results',\n    'pwreset_finish',\n    'passwordreset',\n    'register',\n    'registered',\n    'require_login',\n]\n\n\n@implementer(ILoginForm)\nclass LoginForm(form.EditForm):\n    \"\"\" Implementation of the login form \"\"\"\n\n    fields = field.Fields(ILoginFormSchema)\n\n    id = 'LoginForm'\n    label = _('label_log_in', default=u'Log in')\n\n    ignoreContext = True\n    prefix = ''\n\n    def render(self):\n        registry = queryUtility(IRegistry)\n        ext_login_url = registry['plone.external_login_url']\n        if ext_login_url:\n            return self._handle_external_login(ext_login_url)\n        return self.index()\n\n    def _handle_external_login(self, url):\n        \"\"\"Handle login on this portal where login is external.\"\"\"\n        next_ = self.request.get('next', None)\n        came_from = self.request.get('came_from')\n        urlparts = [part for part in parse.urlparse(url)]\n        qs = dict(parse.parse_qsl(urlparts[4]))\n        portal_url = getToolByName(self.context, 'portal_url')\n        if next_ is not None and not portal_url.isURLInPortal(next_):\n            next_ = None\n        if next_ is not None:\n            qs['next'] = next_\n        if came_from:\n            qs['came_from'] = came_from\n        urlparts[4] = parse.urlencode(qs)\n        self.request.response.redirect(parse.urlunparse(urlparts))\n\n    def _get_auth(self):\n        try:\n            return self.context.acl_users.credentials_cookie_auth\n        except AttributeError:\n            try:\n                return self.context.cookie_authentication\n            except AttributeError:\n                pass\n\n    def updateWidgets(self):\n        auth = self._get_auth()\n\n        if auth:\n            fieldname_name = auth.get('name_cookie', '__ac_name')\n            fieldname_password = auth.get('pw_cookie', '__ac_password')\n        else:\n            fieldname_name = '__ac_name'\n            fieldname_password = '__ac_password'\n\n        self.fields['ac_name'].__name__ = fieldname_name\n        self.fields['ac_password'].__name__ = fieldname_password\n\n        super(LoginForm, self).updateWidgets(prefix='')\n\n        if self.use_email_as_login():\n            self.widgets[fieldname_name].label = _(u'label_email',\n                                                   default=u'Email')\n        self.widgets['came_from'].mode = HIDDEN_MODE\n        self.widgets['came_from'].value = self.get_came_from()\n\n    def get_came_from(self):\n        came_from = self.request.get('came_from', None)\n        if not came_from:\n            came_from = self.request.get('HTTP_REFERER', None)\n            if not came_from:\n                return\n        url_tool = getToolByName(self.context, 'portal_url')\n        if not url_tool.isURLInPortal(came_from):\n            return\n        came_from_path = parse.urlparse(came_from)[2].split('/')\n        for login_template_id in LOGIN_TEMPLATE_IDS:\n            if login_template_id in came_from_path:\n                return\n        return came_from\n\n    def updateActions(self):\n        super(LoginForm, self).updateActions()\n        self.actions['login'].addClass('context')\n\n    def _post_login(self):\n        membership_tool = getToolByName(self.context, 'portal_membership')\n        member = membership_tool.getAuthenticatedMember()\n        must_change_password = member.getProperty('must_change_password', 0)\n        login_time = member.getProperty('login_time', '2000/01/01')\n        if not isinstance(login_time, DateTime):\n            login_time = DateTime(login_time)\n        is_initial_login = login_time == DateTime('2000/01/01')\n\n        membership_tool.loginUser(self.request)\n        if is_initial_login:\n            self.handle_initial_login()\n\n        if must_change_password:\n            self.force_password_change()\n        return is_initial_login\n\n    @button.buttonAndHandler(_('Log in'), name='login')\n    def handleLogin(self, action):\n        data, errors = self.extractData()\n        if errors:\n            self.status = self.formErrorsMessage\n            return\n        membership_tool = getToolByName(self.context, 'portal_membership')\n        status_msg = IStatusMessage(self.request)\n        if membership_tool.isAnonymousUser():\n            self.request.response.expireCookie('__ac', path='/')\n            if self.use_email_as_login():\n                status_msg.addStatusMessage(\n                    _(\n                        u'Login failed. Both email address and password are '\n                        u'case sensitive, check that caps lock is not enabled.'\n                    ),\n                    'error',\n                )\n            else:\n                status_msg.addStatusMessage(\n                    _(\n                        u'Login failed. Both login name and password are case '\n                        u'sensitive, check that caps lock is not enabled.'\n                    ),\n                    'error',\n                )\n            return\n\n        is_initial_login = self._post_login()\n        status_msg.addStatusMessage(\n            _(\n                u'you_are_now_logged_in',\n                default=u'Welcome! You are now logged in.',\n            ),\n            'info'\n        )\n\n        came_from = data.get('came_from', None)\n        self.redirect_after_login(came_from, is_initial_login)\n\n    def handle_initial_login(self):\n        handler = queryMultiAdapter(\n            (self.context, self.request),\n            IInitialLogin,\n        )\n        if handler:\n            handler()\n\n    def force_password_change(self):\n        handler = queryMultiAdapter(\n            (self.context, self.request),\n            IForcePasswordChange,\n        )\n        if handler:\n            handler()\n\n    def redirect_after_login(self, came_from=None, is_initial_login=False):\n        adapter = queryMultiAdapter(\n            (self.context, self.request),\n            IRedirectAfterLogin\n        )\n        if adapter:\n            came_from = adapter(came_from, is_initial_login)\n        if not came_from:\n            came_from = self.context.absolute_url()\n\n        self.request.response.redirect(came_from)\n\n    def self_registration_enabled(self):\n        registry = queryUtility(IRegistry)\n        security_settings = registry.forInterface(\n            ISecuritySchema,\n            prefix='plone'\n        )\n        return security_settings.enable_self_reg\n\n    def use_email_as_login(self):\n        registry = queryUtility(IRegistry)\n        security_settings = registry.forInterface(\n            ISecuritySchema,\n            prefix='plone'\n        )\n        return security_settings.use_email_as_login\n\n\nclass FailsafeLoginForm(LoginForm):\n\n    def render(self):\n        return self.index()\n\n\nclass RequireLoginView(BrowserView):\n\n    def __call__(self):\n        portal_state = getMultiAdapter(\n            (self.context, self.request),\n            name='plone_portal_state',\n        )\n        portal = portal_state.portal()\n        if portal_state.anonymous():\n            url = '{0:s}/login'.format(portal.absolute_url())\n            came_from = self.request.get('came_from', None)\n            if came_from:\n                url += '?came_from={0:s}'.format(parse.quote(came_from))\n        else:\n            url = '{0:s}/insufficient-privileges'.format(portal.absolute_url())\n\n        self.request.response.redirect(url)\n\n\nclass InsufficientPrivilegesView(BrowserView):\n\n    def request_url(self):\n        return self.request.get('came_from')\n\n\nclass InitialLoginPasswordChange(PasswordPanel):\n\n    def render(self):\n        return self.index()\n\n    @button.buttonAndHandler(\n        _(u'label_change_password', default=u'Change Password'),\n        name='reset_passwd',\n    )\n    def action_reset_passwd(self, action):\n        super(InitialLoginPasswordChange, self).action_reset_passwd(\n            self, action)\n        if not action.form.widgets.errors:\n            self.request.response.redirect(self.context.portal_url())\n\n\nclass ForcedPasswordChange(PasswordPanel):\n\n    def render(self):\n        return self.index()\n\n    @button.buttonAndHandler(\n        _(u'label_change_password', default=u'Change Password'),\n        name='reset_passwd',\n    )\n    def action_reset_passwd(self, action):\n        super(ForcedPasswordChange, self).action_reset_passwd(self, action)\n        if not action.form.widgets.errors:\n            membership_tool = getToolByName(self.context, 'portal_membership')\n            member = membership_tool.getAuthenticatedMember()\n            member.setProperties(must_change_password=0)\n            self.request.response.redirect(self.context.portal_url())\n", "path": "Products/CMFPlone/browser/login/login.py"}]}