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