Add transactions support
[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 from ipsilon.info.common import Info
26 from ipsilon.util.cookies import SecureCookie
27 import cherrypy
28
29
30 USERNAME_COOKIE = 'ipsilon_default_username'
31
32
33 class LoginManagerBase(PluginObject, Log):
34
35     def __init__(self):
36         super(LoginManagerBase, self).__init__()
37         self.path = '/'
38         self.next_login = None
39         self.info = None
40
41     def redirect_to_path(self, path):
42         base = cherrypy.config.get('base.mount', "")
43         raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
44
45     def auth_successful(self, username, auth_type=None, userdata=None):
46         # save ref before calling UserSession login() as it
47         # may regenerate the session
48         session = UserSession()
49         ref = session.get_data('login', 'Return')
50         if not ref:
51             ref = cherrypy.config.get('base.mount', "") + '/'
52
53         if self.info:
54             userattrs = self.info.get_user_attrs(username)
55             if userdata:
56                 userdata.update(userattrs or {})
57             else:
58                 userdata = userattrs
59             self.debug("User %s attributes: %s" % (username, repr(userdata)))
60
61         if auth_type:
62             if userdata:
63                 userdata.update({'auth_type': auth_type})
64             else:
65                 userdata = {'auth_type': auth_type}
66
67         session.login(username, userdata)
68
69         # save username into a cookie if parent was form base auth
70         if auth_type == 'password':
71             cookie = SecureCookie(USERNAME_COOKIE, username)
72             # 15 days
73             cookie.maxage = 1296000
74             cookie.send()
75
76         raise cherrypy.HTTPRedirect(ref)
77
78     def auth_failed(self):
79         # try with next module
80         if self.next_login:
81             return self.redirect_to_path(self.next_login.path)
82
83         # return to the caller if any
84         session = UserSession()
85         ref = session.get_data('login', 'Return')
86
87         # otherwise destroy session and return error
88         if not ref:
89             session.logout(None)
90             raise cherrypy.HTTPError(401)
91
92         raise cherrypy.HTTPRedirect(ref)
93
94     def get_tree(self, site):
95         raise NotImplementedError
96
97     def enable(self, site):
98         plugins = site[FACILITY]
99         if self in plugins['enabled']:
100             return
101
102         # configure self
103         if self.name in plugins['config']:
104             self.set_config(plugins['config'][self.name])
105
106         # and add self to the root
107         root = plugins['root']
108         root.add_subtree(self.name, self.get_tree(site))
109
110         # finally add self in login chain
111         prev_obj = None
112         for prev_obj in plugins['enabled']:
113             if prev_obj.next_login:
114                 break
115         if prev_obj:
116             while prev_obj.next_login:
117                 prev_obj = prev_obj.next_login
118             prev_obj.next_login = self
119         if not root.first_login:
120             root.first_login = self
121
122         plugins['enabled'].append(self)
123         self._debug('Login plugin enabled: %s' % self.name)
124
125         # Get handle of the info plugin
126         self.info = root.info
127
128     def disable(self, site):
129         plugins = site[FACILITY]
130         if self not in plugins['enabled']:
131             return
132
133         # remove self from chain
134         root = plugins['root']
135         if root.first_login == self:
136             root.first_login = self.next_login
137         elif root.first_login:
138             prev_obj = root.first_login
139             while prev_obj.next_login != self:
140                 prev_obj = prev_obj.next_login
141             if prev_obj:
142                 prev_obj.next_login = self.next_login
143         self.next_login = None
144
145         plugins['enabled'].remove(self)
146         self._debug('Login plugin disabled: %s' % self.name)
147
148
149 class LoginPageBase(Page):
150
151     def __init__(self, site, mgr):
152         super(LoginPageBase, self).__init__(site)
153         self.lm = mgr
154
155     def root(self, *args, **kwargs):
156         raise cherrypy.HTTPError(500)
157
158
159 class LoginFormBase(LoginPageBase):
160
161     def __init__(self, site, mgr, page, template=None):
162         super(LoginFormBase, self).__init__(site, mgr)
163         self.formpage = page
164         self.formtemplate = template or 'login/form.html'
165
166     def GET(self, *args, **kwargs):
167         context = self.create_tmpl_context()
168         # pylint: disable=star-args
169         return self._template(self.formtemplate, **context)
170
171     def root(self, *args, **kwargs):
172         op = getattr(self, cherrypy.request.method, self.GET)
173         if callable(op):
174             return op(*args, **kwargs)
175
176     def create_tmpl_context(self, **kwargs):
177         next_url = None
178         if self.lm.next_login is not None:
179             next_url = self.lm.next_login.path
180
181         cookie = SecureCookie(USERNAME_COOKIE)
182         cookie.receive()
183         username = cookie.value
184         if username is None:
185             username = ''
186
187         context = {
188             "title": 'Login',
189             "action": '%s/%s' % (self.basepath, self.formpage),
190             "service_name": self.lm.service_name,
191             "username_text": self.lm.username_text,
192             "password_text": self.lm.password_text,
193             "description": self.lm.help_text,
194             "next_url": next_url,
195             "username": username,
196         }
197         context.update(kwargs)
198         return context
199
200
201 FACILITY = 'login_config'
202
203
204 class Login(Page):
205
206     def __init__(self, *args, **kwargs):
207         super(Login, self).__init__(*args, **kwargs)
208         self.first_login = None
209         self.info = Info(self._site)
210
211         loader = PluginLoader(Login, FACILITY, 'LoginManager')
212         self._site[FACILITY] = loader.get_plugin_data()
213         plugins = self._site[FACILITY]
214
215         available = plugins['available'].keys()
216         self._debug('Available login managers: %s' % str(available))
217
218         plugins['root'] = self
219         for item in plugins['whitelist']:
220             self._debug('Login plugin in whitelist: %s' % item)
221             if item not in plugins['available']:
222                 continue
223             plugins['available'][item].enable(self._site)
224
225     def add_subtree(self, name, page):
226         self.__dict__[name] = page
227
228     def root(self, *args, **kwargs):
229         if self.first_login:
230             raise cherrypy.HTTPRedirect('%s/login/%s' %
231                                         (self.basepath,
232                                          self.first_login.path))
233         return self._template('login/index.html', title='Login')
234
235
236 class Logout(Page):
237
238     def root(self, *args, **kwargs):
239         UserSession().logout(self.user)
240         return self._template('logout.html', title='Logout')
241
242
243 class LoginMgrsInstall(object):
244
245     def __init__(self):
246         pi = PluginInstaller(LoginMgrsInstall)
247         self.plugins = pi.get_plugins()