Fix some copy-paste errors in help output
[cascardo/ipsilon.git] / ipsilon / login / common.py
1 # Copyright (C) 2013  Simo Sorce <simo@redhat.com>
2 #
3 # see file 'COPYING' for use and warranty information
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 from ipsilon.util.page import Page
19 from ipsilon.util.user import UserSession
20 from ipsilon.util.plugin import PluginInstaller, PluginLoader
21 from ipsilon.util.plugin import PluginObject, PluginConfig
22 from ipsilon.info.common import Info
23 from ipsilon.util.cookies import SecureCookie
24 import cherrypy
25
26
27 USERNAME_COOKIE = 'ipsilon_default_username'
28
29
30 class LoginManagerBase(PluginConfig, PluginObject):
31
32     def __init__(self, *args):
33         PluginConfig.__init__(self)
34         PluginObject.__init__(self, *args)
35         self._root = None
36         self._site = None
37         self.path = '/'
38         self.info = None
39
40     def redirect_to_path(self, path):
41         base = cherrypy.config.get('base.mount', "")
42         raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
43
44     def auth_successful(self, trans, username, auth_type=None, userdata=None):
45         session = UserSession()
46
47         if self.info:
48             userattrs = self.info.get_user_attrs(username)
49             if userdata:
50                 userdata.update(userattrs.get('userdata', {}))
51             else:
52                 userdata = userattrs.get('userdata', {})
53
54             # merge groups and extras from login plugin and info plugin
55             userdata['groups'] = list(set(userdata.get('groups', []) +
56                                           userattrs.get('groups', [])))
57
58             userdata['extras'] = userdata.get('extras', {})
59             userdata['extras'].update(userattrs.get('extras', {}))
60
61             self.debug("User %s attributes: %s" % (username, repr(userdata)))
62
63         if auth_type:
64             if userdata:
65                 userdata.update({'auth_type': auth_type})
66             else:
67                 userdata = {'auth_type': auth_type}
68
69         # create session login including all the userdata just gathered
70         session.login(username, userdata)
71
72         # save username into a cookie if parent was form base auth
73         if auth_type == 'password':
74             cookie = SecureCookie(USERNAME_COOKIE, username)
75             # 15 days
76             cookie.maxage = 1296000
77             cookie.send()
78
79         transdata = trans.retrieve()
80         self.debug(transdata)
81         redirect = transdata.get('login_return',
82                                  cherrypy.config.get('base.mount', "") + '/')
83         self.debug('Redirecting back to: %s' % redirect)
84
85         # on direct login the UI (ie not redirected by a provider) we ned to
86         # remove the transaction cookie as it won't be needed anymore
87         if trans.provider == 'login':
88             self.debug('Wiping transaction data')
89             trans.wipe()
90         raise cherrypy.HTTPRedirect(redirect)
91
92     def auth_failed(self, trans):
93         # try with next module
94         next_login = self.next_login()
95         if next_login:
96             return self.redirect_to_path(next_login.path)
97
98         # return to the caller if any
99         session = UserSession()
100
101         transdata = trans.retrieve()
102
103         # on direct login the UI (ie not redirected by a provider) we ned to
104         # remove the transaction cookie as it won't be needed anymore
105         if trans.provider == 'login':
106             trans.wipe()
107
108         # destroy session and return error
109         if 'login_return' not in transdata:
110             session.logout(None)
111             raise cherrypy.HTTPError(401)
112
113         raise cherrypy.HTTPRedirect(transdata['login_return'])
114
115     def get_tree(self, site):
116         raise NotImplementedError
117
118     def register(self, root, site):
119         self._root = root
120         self._site = site
121
122     def next_login(self):
123         plugins = self._site[FACILITY]
124         try:
125             idx = plugins.enabled.index(self.name)
126             item = plugins.enabled[idx + 1]
127             return plugins.available[item]
128         except (ValueError, IndexError):
129             return None
130
131     def on_enable(self):
132
133         # and add self to the root
134         self._root.add_subtree(self.name, self.get_tree(self._site))
135
136         # Get handle of the info plugin
137         self.info = self._root.info
138
139
140 class LoginPageBase(Page):
141
142     def __init__(self, site, mgr):
143         super(LoginPageBase, self).__init__(site)
144         self.lm = mgr
145         self._Transaction = None
146
147     def root(self, *args, **kwargs):
148         raise cherrypy.HTTPError(500)
149
150
151 class LoginFormBase(LoginPageBase):
152
153     def __init__(self, site, mgr, page, template=None):
154         super(LoginFormBase, self).__init__(site, mgr)
155         self.formpage = page
156         self.formtemplate = template or 'login/form.html'
157         self.trans = None
158
159     def GET(self, *args, **kwargs):
160         context = self.create_tmpl_context()
161         # pylint: disable=star-args
162         return self._template(self.formtemplate, **context)
163
164     def root(self, *args, **kwargs):
165         self.trans = self.get_valid_transaction('login', **kwargs)
166         op = getattr(self, cherrypy.request.method, self.GET)
167         if callable(op):
168             return op(*args, **kwargs)
169
170     def create_tmpl_context(self, **kwargs):
171         next_url = None
172         next_login = self.lm.next_login()
173         if next_login:
174             next_url = '%s?%s' % (next_login.path,
175                                   self.trans.get_GET_arg())
176
177         cookie = SecureCookie(USERNAME_COOKIE)
178         cookie.receive()
179         username = cookie.value
180
181         target = None
182         if self.trans is not None:
183             tid = self.trans.transaction_id
184             target = self.trans.retrieve().get('login_target')
185             username = self.trans.retrieve().get('login_username')
186         if tid is None:
187             tid = ''
188
189         if username is None:
190             username = ''
191
192         context = {
193             "title": 'Login',
194             "action": '%s/%s' % (self.basepath, self.formpage),
195             "service_name": self.lm.service_name,
196             "username_text": self.lm.username_text,
197             "password_text": self.lm.password_text,
198             "description": self.lm.help_text,
199             "next_url": next_url,
200             "username": username,
201             "login_target": target,
202             "cancel_url": '%s/login/cancel?%s' % (self.basepath,
203                                                   self.trans.get_GET_arg()),
204         }
205         context.update(kwargs)
206         if self.trans is not None:
207             t = self.trans.get_POST_tuple()
208             context.update({t[0]: t[1]})
209
210         return context
211
212
213 FACILITY = 'login_config'
214
215
216 class Login(Page):
217
218     def __init__(self, *args, **kwargs):
219         super(Login, self).__init__(*args, **kwargs)
220         self.cancel = Cancel(*args, **kwargs)
221         self.info = Info(self._site)
222
223         plugins = PluginLoader(Login, FACILITY, 'LoginManager')
224         plugins.get_plugin_data()
225         self._site[FACILITY] = plugins
226
227         available = plugins.available.keys()
228         self._debug('Available login managers: %s' % str(available))
229
230         for item in plugins.available:
231             plugin = plugins.available[item]
232             plugin.register(self, self._site)
233
234         for item in plugins.enabled:
235             self._debug('Login plugin in enabled list: %s' % item)
236             if item not in plugins.available:
237                 continue
238             plugins.available[item].enable()
239
240     def add_subtree(self, name, page):
241         self.__dict__[name] = page
242
243     def get_first_login(self):
244         plugin = None
245         plugins = self._site[FACILITY]
246         if plugins.enabled:
247             first = plugins.enabled[0]
248             plugin = plugins.available[first]
249         return plugin
250
251     def root(self, *args, **kwargs):
252         plugin = self.get_first_login()
253         if plugin:
254             trans = self.get_valid_transaction('login', **kwargs)
255             redirect = '%s/login/%s?%s' % (self.basepath,
256                                            plugin.path,
257                                            trans.get_GET_arg())
258             raise cherrypy.HTTPRedirect(redirect)
259         return self._template('login/index.html', title='Login')
260
261
262 class Logout(Page):
263
264     def root(self, *args, **kwargs):
265         UserSession().logout(self.user)
266         return self._template('logout.html', title='Logout')
267
268
269 class Cancel(Page):
270
271     def GET(self, *args, **kwargs):
272
273         session = UserSession()
274         session.logout(None)
275
276         # return to the caller if any
277         transdata = self.get_valid_transaction('login', **kwargs).retrieve()
278         if 'login_return' not in transdata:
279             raise cherrypy.HTTPError(401)
280         raise cherrypy.HTTPRedirect(transdata['login_return'])
281
282     def root(self, *args, **kwargs):
283         op = getattr(self, cherrypy.request.method, self.GET)
284         if callable(op):
285             return op(*args, **kwargs)
286
287
288 class LoginMgrsInstall(object):
289
290     def __init__(self):
291         pi = PluginInstaller(LoginMgrsInstall, FACILITY)
292         self.plugins = pi.get_plugins()