1 # Copyright (C) 2013 Simo Sorce <simo@redhat.com>
3 # see file 'COPYING' for use and warranty information
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.
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.
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/>.
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
27 USERNAME_COOKIE = 'ipsilon_default_username'
30 class LoginManagerBase(PluginConfig, PluginObject):
32 def __init__(self, *args):
33 PluginConfig.__init__(self)
34 PluginObject.__init__(self, *args)
40 def redirect_to_path(self, path):
41 base = cherrypy.config.get('base.mount', "")
42 raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
44 def auth_successful(self, trans, username, auth_type=None, userdata=None):
45 session = UserSession()
47 # merge attributes from login plugin and info plugin
49 infoattrs = self.info.get_user_attrs(username)
56 if '_groups' in infoattrs:
57 userdata['_groups'] = list(set(userdata.get('_groups', []) +
58 infoattrs['_groups']))
59 del infoattrs['_groups']
61 if '_extras' in infoattrs:
62 userdata['_extras'] = userdata.get('_extras', {})
63 userdata['_extras'].update(infoattrs['_extras'])
64 del infoattrs['_extras']
66 userdata.update(infoattrs)
68 self.debug("User %s attributes: %s" % (username, repr(userdata)))
72 userdata.update({'_auth_type': auth_type})
74 userdata = {'_auth_type': auth_type}
76 # create session login including all the userdata just gathered
77 session.login(username, userdata)
79 # save username into a cookie if parent was form base auth
80 if auth_type == 'password':
81 cookie = SecureCookie(USERNAME_COOKIE, username)
83 cookie.maxage = 1296000
86 transdata = trans.retrieve()
88 redirect = transdata.get('login_return',
89 cherrypy.config.get('base.mount', "") + '/')
90 self.debug('Redirecting back to: %s' % redirect)
92 # on direct login the UI (ie not redirected by a provider) we ned to
93 # remove the transaction cookie as it won't be needed anymore
94 if trans.provider == 'login':
95 self.debug('Wiping transaction data')
97 raise cherrypy.HTTPRedirect(redirect)
99 def auth_failed(self, trans):
100 # try with next module
101 next_login = self.next_login()
103 return self.redirect_to_path(next_login.path)
105 # return to the caller if any
106 session = UserSession()
108 transdata = trans.retrieve()
110 # on direct login the UI (ie not redirected by a provider) we ned to
111 # remove the transaction cookie as it won't be needed anymore
112 if trans.provider == 'login':
115 # destroy session and return error
116 if 'login_return' not in transdata:
118 raise cherrypy.HTTPError(401)
120 raise cherrypy.HTTPRedirect(transdata['login_return'])
122 def get_tree(self, site):
123 raise NotImplementedError
125 def register(self, root, site):
129 def next_login(self):
130 plugins = self._site[FACILITY]
132 idx = plugins.enabled.index(self.name)
133 item = plugins.enabled[idx + 1]
134 return plugins.available[item]
135 except (ValueError, IndexError):
140 # and add self to the root
141 self._root.add_subtree(self.name, self.get_tree(self._site))
143 # Get handle of the info plugin
144 self.info = self._root.info
147 class LoginPageBase(Page):
149 def __init__(self, site, mgr):
150 super(LoginPageBase, self).__init__(site)
152 self._Transaction = None
154 def root(self, *args, **kwargs):
155 raise cherrypy.HTTPError(500)
158 class LoginFormBase(LoginPageBase):
160 def __init__(self, site, mgr, page, template=None):
161 super(LoginFormBase, self).__init__(site, mgr)
163 self.formtemplate = template or 'login/form.html'
166 def GET(self, *args, **kwargs):
167 context = self.create_tmpl_context()
168 # pylint: disable=star-args
169 return self._template(self.formtemplate, **context)
171 def root(self, *args, **kwargs):
172 self.trans = self.get_valid_transaction('login', **kwargs)
173 op = getattr(self, cherrypy.request.method, self.GET)
175 return op(*args, **kwargs)
177 def create_tmpl_context(self, **kwargs):
179 next_login = self.lm.next_login()
181 next_url = '%s?%s' % (next_login.path,
182 self.trans.get_GET_arg())
184 cookie = SecureCookie(USERNAME_COOKIE)
186 username = cookie.value
189 if self.trans is not None:
190 tid = self.trans.transaction_id
191 target = self.trans.retrieve().get('login_target')
192 username = self.trans.retrieve().get('login_username')
201 "action": '%s/%s' % (self.basepath, self.formpage),
202 "service_name": self.lm.service_name,
203 "username_text": self.lm.username_text,
204 "password_text": self.lm.password_text,
205 "description": self.lm.help_text,
206 "next_url": next_url,
207 "username": username,
208 "login_target": target,
209 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
210 self.trans.get_GET_arg()),
212 context.update(kwargs)
213 if self.trans is not None:
214 t = self.trans.get_POST_tuple()
215 context.update({t[0]: t[1]})
220 FACILITY = 'login_config'
225 def __init__(self, *args, **kwargs):
226 super(Login, self).__init__(*args, **kwargs)
227 self.cancel = Cancel(*args, **kwargs)
228 self.info = Info(self._site)
230 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
231 plugins.get_plugin_data()
232 self._site[FACILITY] = plugins
234 available = plugins.available.keys()
235 self._debug('Available login managers: %s' % str(available))
237 for item in plugins.available:
238 plugin = plugins.available[item]
239 plugin.register(self, self._site)
241 for item in plugins.enabled:
242 self._debug('Login plugin in enabled list: %s' % item)
243 if item not in plugins.available:
245 plugins.available[item].enable()
247 def add_subtree(self, name, page):
248 self.__dict__[name] = page
250 def get_first_login(self):
252 plugins = self._site[FACILITY]
254 first = plugins.enabled[0]
255 plugin = plugins.available[first]
258 def root(self, *args, **kwargs):
259 plugin = self.get_first_login()
261 trans = self.get_valid_transaction('login', **kwargs)
262 redirect = '%s/login/%s?%s' % (self.basepath,
265 raise cherrypy.HTTPRedirect(redirect)
266 return self._template('login/index.html', title='Login')
271 def root(self, *args, **kwargs):
272 UserSession().logout(self.user)
273 return self._template('logout.html', title='Logout')
278 def GET(self, *args, **kwargs):
280 session = UserSession()
283 # return to the caller if any
284 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
285 if 'login_return' not in transdata:
286 raise cherrypy.HTTPError(401)
287 raise cherrypy.HTTPRedirect(transdata['login_return'])
289 def root(self, *args, **kwargs):
290 op = getattr(self, cherrypy.request.method, self.GET)
292 return op(*args, **kwargs)
295 class LoginManagerInstaller(object):
297 self.facility = FACILITY
301 def unconfigure(self, opts):
304 def install_args(self, group):
305 raise NotImplementedError
307 def configure(self, opts):
308 raise NotImplementedError
311 class LoginMgrsInstall(object):
314 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
315 self.plugins = pi.get_plugins()