#!/usr/bin/python # # Copyright (C) 2013 Simo Sorce # # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from ipsilon.util.page import Page from ipsilon.util.user import UserSession from ipsilon.util.plugin import PluginInstaller, PluginLoader from ipsilon.util.plugin import PluginObject, PluginConfig from ipsilon.info.common import Info from ipsilon.util.cookies import SecureCookie import cherrypy USERNAME_COOKIE = 'ipsilon_default_username' class LoginManagerBase(PluginConfig, PluginObject): def __init__(self): PluginConfig.__init__(self) PluginObject.__init__(self) self._site = None self.path = '/' self.next_login = None self.info = None self.is_enabled = False def redirect_to_path(self, path): base = cherrypy.config.get('base.mount', "") raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path)) def auth_successful(self, trans, username, auth_type=None, userdata=None): session = UserSession() if self.info: userattrs = self.info.get_user_attrs(username) if userdata: userdata.update(userattrs.get('userdata', {})) else: userdata = userattrs.get('userdata', {}) # merge groups and extras from login plugin and info plugin userdata['groups'] = list(set(userdata.get('groups', []) + userattrs.get('groups', []))) userdata['extras'] = userdata.get('extras', {}) userdata['extras'].update(userattrs.get('extras', {})) self.debug("User %s attributes: %s" % (username, repr(userdata))) if auth_type: if userdata: userdata.update({'auth_type': auth_type}) else: userdata = {'auth_type': auth_type} # create session login including all the userdata just gathered session.login(username, userdata) # save username into a cookie if parent was form base auth if auth_type == 'password': cookie = SecureCookie(USERNAME_COOKIE, username) # 15 days cookie.maxage = 1296000 cookie.send() transdata = trans.retrieve() self.debug(transdata) redirect = transdata.get('login_return', cherrypy.config.get('base.mount', "") + '/') self.debug('Redirecting back to: %s' % redirect) # on direct login the UI (ie not redirected by a provider) we ned to # remove the transaction cookie as it won't be needed anymore if trans.provider == 'login': self.debug('Wiping transaction data') trans.wipe() raise cherrypy.HTTPRedirect(redirect) def auth_failed(self, trans): # try with next module if self.next_login: return self.redirect_to_path(self.next_login.path) # return to the caller if any session = UserSession() transdata = trans.retrieve() # on direct login the UI (ie not redirected by a provider) we ned to # remove the transaction cookie as it won't be needed anymore if trans.provider == 'login': trans.wipe() # destroy session and return error if 'login_return' not in transdata: session.logout(None) raise cherrypy.HTTPError(401) raise cherrypy.HTTPRedirect(transdata['login_return']) def get_tree(self, site): raise NotImplementedError def enable(self, site): if self.is_enabled: return if not self._site: self._site = site plugins = self._site[FACILITY] # configure self if self.name in plugins['config']: self.import_config(plugins['config'][self.name]) # and add self to the root root = plugins['root'] root.add_subtree(self.name, self.get_tree(site)) # finally add self in login chain prev_obj = None for prev_obj in plugins['enabled']: if prev_obj.next_login: break if prev_obj: while prev_obj.next_login: prev_obj = prev_obj.next_login prev_obj.next_login = self if not root.first_login: root.first_login = self plugins['enabled'].append(self) self.is_enabled = True self._debug('Login plugin enabled: %s' % self.name) # Get handle of the info plugin self.info = root.info def disable(self, site): if not self.is_enabled: return plugins = self._site[FACILITY] # remove self from chain root = plugins['root'] if root.first_login == self: root.first_login = self.next_login elif root.first_login: prev_obj = root.first_login while prev_obj.next_login != self: prev_obj = prev_obj.next_login if prev_obj: prev_obj.next_login = self.next_login self.next_login = None plugins['enabled'].remove(self) self.is_enabled = False self._debug('Login plugin disabled: %s' % self.name) class LoginPageBase(Page): def __init__(self, site, mgr): super(LoginPageBase, self).__init__(site) self.lm = mgr self._Transaction = None def root(self, *args, **kwargs): raise cherrypy.HTTPError(500) class LoginFormBase(LoginPageBase): def __init__(self, site, mgr, page, template=None): super(LoginFormBase, self).__init__(site, mgr) self.formpage = page self.formtemplate = template or 'login/form.html' self.trans = None def GET(self, *args, **kwargs): context = self.create_tmpl_context() # pylint: disable=star-args return self._template(self.formtemplate, **context) def root(self, *args, **kwargs): self.trans = self.get_valid_transaction('login', **kwargs) op = getattr(self, cherrypy.request.method, self.GET) if callable(op): return op(*args, **kwargs) def create_tmpl_context(self, **kwargs): next_url = None if self.lm.next_login is not None: next_url = '%s?%s' % (self.lm.next_login.path, self.trans.get_GET_arg()) cookie = SecureCookie(USERNAME_COOKIE) cookie.receive() username = cookie.value if username is None: username = '' target = None if self.trans is not None: tid = self.trans.transaction_id target = self.trans.retrieve().get('login_target') if tid is None: tid = '' context = { "title": 'Login', "action": '%s/%s' % (self.basepath, self.formpage), "service_name": self.lm.service_name, "username_text": self.lm.username_text, "password_text": self.lm.password_text, "description": self.lm.help_text, "next_url": next_url, "username": username, "login_target": target, "cancel_url": '%s/login/cancel?%s' % (self.basepath, self.trans.get_GET_arg()), } context.update(kwargs) if self.trans is not None: t = self.trans.get_POST_tuple() context.update({t[0]: t[1]}) return context FACILITY = 'login_config' class Login(Page): def __init__(self, *args, **kwargs): super(Login, self).__init__(*args, **kwargs) self.cancel = Cancel(*args, **kwargs) self.first_login = None self.info = Info(self._site) loader = PluginLoader(Login, FACILITY, 'LoginManager') self._site[FACILITY] = loader.get_plugin_data() plugins = self._site[FACILITY] available = plugins['available'].keys() self._debug('Available login managers: %s' % str(available)) plugins['root'] = self for item in plugins['whitelist']: self._debug('Login plugin in whitelist: %s' % item) if item not in plugins['available']: continue plugins['available'][item].enable(self._site) def add_subtree(self, name, page): self.__dict__[name] = page def root(self, *args, **kwargs): if self.first_login: trans = self.get_valid_transaction('login', **kwargs) redirect = '%s/login/%s?%s' % (self.basepath, self.first_login.path, trans.get_GET_arg()) raise cherrypy.HTTPRedirect(redirect) return self._template('login/index.html', title='Login') class Logout(Page): def root(self, *args, **kwargs): UserSession().logout(self.user) return self._template('logout.html', title='Logout') class Cancel(Page): def GET(self, *args, **kwargs): session = UserSession() session.logout(None) # return to the caller if any transdata = self.get_valid_transaction('login', **kwargs).retrieve() if 'login_return' not in transdata: raise cherrypy.HTTPError(401) raise cherrypy.HTTPRedirect(transdata['login_return']) def root(self, *args, **kwargs): op = getattr(self, cherrypy.request.method, self.GET) if callable(op): return op(*args, **kwargs) class LoginMgrsInstall(object): def __init__(self): pi = PluginInstaller(LoginMgrsInstall) self.plugins = pi.get_plugins()