1from tg.controllers.util import _build_url
2
3try:
4    from urlparse import urlparse, urlunparse, parse_qs
5except ImportError: #pragma: no cover
6    from urllib.parse import urlparse, urlunparse, parse_qs
7
8try:
9    from urllib import urlencode
10except ImportError: #pragma: no cover
11    from urllib.parse import urlencode
12
13from webob import Request
14from webob.exc import HTTPFound, HTTPUnauthorized
15from zope.interface import implementer
16
17from repoze.who.interfaces import IChallenger, IIdentifier
18
19@implementer(IChallenger, IIdentifier)
20class FastFormPlugin(object):
21    """
22    Simplified and faster version of the repoze.who.friendlyforms
23    FriendlyForm plugin. The FastForm version works only with UTF-8
24    content which is the default for new WebOb versions.
25    """
26    classifications = {
27        IIdentifier: ["browser"],
28        IChallenger: ["browser"],
29        }
30
31    def __init__(self, login_form_url, login_handler_path, post_login_url,
32                 logout_handler_path, post_logout_url, rememberer_name,
33                 login_counter_name=None):
34        """
35        :param login_form_url: The URL/path where the login form is located.
36        :type login_form_url: str
37        :param login_handler_path: The URL/path where the login form is
38            submitted to (where it is processed by this plugin).
39        :type login_handler_path: str
40        :param post_login_url: The URL/path where the user should be redirected
41            to after login (even if wrong credentials were provided).
42        :type post_login_url: str
43        :param logout_handler_path: The URL/path where the user is logged out.
44        :type logout_handler_path: str
45        :param post_logout_url: The URL/path where the user should be
46            redirected to after logout.
47        :type post_logout_url: str
48        :param rememberer_name: The name of the repoze.who identifier which
49            acts as rememberer.
50        :type rememberer_name: str
51        """
52        self.login_form_url = login_form_url
53        self.login_handler_path = login_handler_path
54        self.post_login_url = post_login_url
55        self.logout_handler_path = logout_handler_path
56        self.post_logout_url = post_logout_url
57        self.rememberer_name = rememberer_name
58
59        if not login_counter_name:
60            login_counter_name = '__logins'
61        self.login_counter_name = login_counter_name
62
63    # IIdentifier
64    def identify(self, environ):
65        path_info = environ['PATH_INFO']
66
67        if path_info == self.login_handler_path:
68            query = self._get_form_data(environ)
69
70            try:
71                credentials = {'login': query['login'],
72                               'password': query['password'],
73                               'max_age':query.get('remember')}
74            except KeyError:
75                credentials = None
76
77            params = {}
78            if 'came_from' in query:
79                params['came_from'] = query['came_from']
80            if self.login_counter_name is not None and self.login_counter_name in query:
81                params[self.login_counter_name] = query[self.login_counter_name]
82
83            destination = _build_url(environ, self.post_login_url, params=params)
84            environ['repoze.who.application'] = HTTPFound(location=destination)
85            return credentials
86
87        elif path_info == self.logout_handler_path:
88            query = self._get_form_data(environ)
89            came_from = query.get('came_from')
90            if came_from is None:
91                came_from = _build_url(environ, '/')
92
93            # set in environ for self.challenge() to find later
94            environ['came_from'] = came_from
95            environ['repoze.who.application'] = HTTPUnauthorized()
96
97        elif path_info in (self.login_form_url, self.post_login_url):
98            query = self._get_form_data(environ)
99            environ['repoze.who.logins'] = 0
100
101            if self.login_counter_name is not None and self.login_counter_name in query:
102                environ['repoze.who.logins'] = int(query[self.login_counter_name])
103                del query[self.login_counter_name]
104                environ['QUERY_STRING'] = urlencode(query, doseq=True)
105
106        return None
107
108    # IChallenger
109    def challenge(self, environ, status, app_headers, forget_headers):
110        path_info = environ['PATH_INFO']
111
112        # Configuring the headers to be set:
113        cookies = [(h,v) for (h,v) in app_headers if h.lower() == 'set-cookie']
114        headers = forget_headers + cookies
115
116        if path_info == self.logout_handler_path:
117            params = {}
118            if 'came_from' in environ:
119                params.update({'came_from':environ['came_from']})
120            destination = _build_url(environ, self.post_logout_url, params=params)
121
122        else:
123            came_from_params = parse_qs(environ.get('QUERY_STRING', ''))
124            params = {'came_from': _build_url(environ, path_info, came_from_params)}
125            destination = _build_url(environ, self.login_form_url, params=params)
126
127        return HTTPFound(location=destination, headers=headers)
128
129    # IIdentifier
130    def remember(self, environ, identity):
131        rememberer = self._get_rememberer(environ)
132        return rememberer.remember(environ, identity)
133
134    # IIdentifier
135    def forget(self, environ, identity):
136        rememberer = self._get_rememberer(environ)
137        return rememberer.forget(environ, identity)
138
139    def _get_rememberer(self, environ):
140        rememberer = environ['repoze.who.plugins'][self.rememberer_name]
141        return rememberer
142
143    def _get_form_data(self, environ):
144        request = Request(environ)
145        query = dict(request.GET)
146        query.update(request.POST)
147        return query
148
149    def __repr__(self):
150        return '<%s:%s %s>' % (self.__class__.__name__, self.login_handler_path, id(self))
151