Use helper cookie to remember the username
[cascardo/ipsilon.git] / ipsilon / login / common.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2013  Simo Sorce <simo@redhat.com>
4 #
5 # see file 'COPYING' for use and warranty information
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 from ipsilon.util.log import Log
21 from ipsilon.util.page import Page
22 from ipsilon.util.user import UserSession
23 from ipsilon.util.plugin import PluginLoader, PluginObject
24 from ipsilon.util.plugin import PluginInstaller
25 import cherrypy
26
27
28 USERNAME_COOKIE = 'ipsilon_default_username'
29
30
31 class LoginManagerBase(PluginObject, Log):
32
33     def __init__(self):
34         super(LoginManagerBase, self).__init__()
35         self.path = '/'
36         self.next_login = None
37
38     def redirect_to_path(self, path):
39         base = cherrypy.config.get('base.mount', "")
40         raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
41
42     def auth_successful(self, username, auth_type=None, userdata=None):
43         # save ref before calling UserSession login() as it
44         # may regenerate the session
45         session = UserSession()
46         ref = session.get_data('login', 'Return')
47         if not ref:
48             ref = cherrypy.config.get('base.mount', "") + '/'
49
50         if auth_type:
51             if userdata:
52                 userdata.update({'auth_type': auth_type})
53             else:
54                 userdata = {'auth_type': auth_type}
55
56         session.login(username, userdata)
57
58         # save username into a cookie if parent was form base auth
59         if auth_type == 'password':
60             cherrypy.response.cookie[USERNAME_COOKIE] = username
61             cherrypy.response.cookie[USERNAME_COOKIE]['path'] = \
62                 cherrypy.config.get('base.mount', '/')
63             cherrypy.response.cookie[USERNAME_COOKIE]['secure'] = True
64             cherrypy.response.cookie[USERNAME_COOKIE]['httponly'] = True
65             # 15 days
66             cherrypy.response.cookie[USERNAME_COOKIE]['max-age'] = 1296000
67
68         raise cherrypy.HTTPRedirect(ref)
69
70     def auth_failed(self):
71         # try with next module
72         if self.next_login:
73             return self.redirect_to_path(self.next_login.path)
74
75         # return to the caller if any
76         session = UserSession()
77         ref = session.get_data('login', 'Return')
78
79         # otherwise destroy session and return error
80         if not ref:
81             session.logout(None)
82             raise cherrypy.HTTPError(401)
83
84         raise cherrypy.HTTPRedirect(ref)
85
86     def get_tree(self, site):
87         raise NotImplementedError
88
89     def enable(self, site):
90         plugins = site[FACILITY]
91         if self in plugins['enabled']:
92             return
93
94         # configure self
95         if self.name in plugins['config']:
96             self.set_config(plugins['config'][self.name])
97
98         # and add self to the root
99         root = plugins['root']
100         root.add_subtree(self.name, self.get_tree(site))
101
102         # finally add self in login chain
103         prev_obj = None
104         for prev_obj in plugins['enabled']:
105             if prev_obj.next_login:
106                 break
107         if prev_obj:
108             while prev_obj.next_login:
109                 prev_obj = prev_obj.next_login
110             prev_obj.next_login = self
111         if not root.first_login:
112             root.first_login = self
113
114         plugins['enabled'].append(self)
115         self._debug('Login plugin enabled: %s' % self.name)
116
117     def disable(self, site):
118         plugins = site[FACILITY]
119         if self not in plugins['enabled']:
120             return
121
122         # remove self from chain
123         root = plugins['root']
124         if root.first_login == self:
125             root.first_login = self.next_login
126         elif root.first_login:
127             prev_obj = root.first_login
128             while prev_obj.next_login != self:
129                 prev_obj = prev_obj.next_login
130             if prev_obj:
131                 prev_obj.next_login = self.next_login
132         self.next_login = None
133
134         plugins['enabled'].remove(self)
135         self._debug('Login plugin disabled: %s' % self.name)
136
137
138 class LoginPageBase(Page):
139
140     def __init__(self, site, mgr):
141         super(LoginPageBase, self).__init__(site)
142         self.lm = mgr
143
144     def root(self, *args, **kwargs):
145         raise cherrypy.HTTPError(500)
146
147
148 class LoginFormBase(LoginPageBase):
149
150     def __init__(self, site, mgr, page, template=None):
151         super(LoginFormBase, self).__init__(site, mgr)
152         self.formpage = page
153         self.formtemplate = template or 'login/form.html'
154
155     def GET(self, *args, **kwargs):
156         context = self.create_tmpl_context()
157         # pylint: disable=star-args
158         return self._template(self.formtemplate, **context)
159
160     def root(self, *args, **kwargs):
161         op = getattr(self, cherrypy.request.method, self.GET)
162         if callable(op):
163             return op(*args, **kwargs)
164
165     def create_tmpl_context(self, **kwargs):
166         next_url = None
167         if self.lm.next_login is not None:
168             next_url = self.lm.next_login.path
169
170         username = ''
171         if USERNAME_COOKIE in cherrypy.request.cookie:
172             username = cherrypy.request.cookie[USERNAME_COOKIE].value
173
174         context = {
175             "title": 'Login',
176             "action": '%s/%s' % (self.basepath, self.formpage),
177             "service_name": self.lm.service_name,
178             "username_text": self.lm.username_text,
179             "password_text": self.lm.password_text,
180             "description": self.lm.help_text,
181             "next_url": next_url,
182             "username": username,
183         }
184         context.update(kwargs)
185         return context
186
187
188 FACILITY = 'login_config'
189
190
191 class Login(Page):
192
193     def __init__(self, *args, **kwargs):
194         super(Login, self).__init__(*args, **kwargs)
195         self.first_login = None
196
197         loader = PluginLoader(Login, FACILITY, 'LoginManager')
198         self._site[FACILITY] = loader.get_plugin_data()
199         plugins = self._site[FACILITY]
200
201         available = plugins['available'].keys()
202         self._debug('Available login managers: %s' % str(available))
203
204         plugins['root'] = self
205         for item in plugins['whitelist']:
206             self._debug('Login plugin in whitelist: %s' % item)
207             if item not in plugins['available']:
208                 continue
209             plugins['available'][item].enable(self._site)
210
211     def add_subtree(self, name, page):
212         self.__dict__[name] = page
213
214     def root(self, *args, **kwargs):
215         if self.first_login:
216             raise cherrypy.HTTPRedirect('%s/login/%s' %
217                                         (self.basepath,
218                                          self.first_login.path))
219         return self._template('login/index.html', title='Login')
220
221
222 class Logout(Page):
223
224     def root(self, *args, **kwargs):
225         UserSession().logout(self.user)
226         return self._template('logout.html', title='Logout')
227
228
229 class LoginMgrsInstall(object):
230
231     def __init__(self):
232         pi = PluginInstaller(LoginMgrsInstall)
233         self.plugins = pi.get_plugins()