-#!/usr/bin/python
-#
# Copyright (C) 2013 Simo Sorce <simo@redhat.com>
#
# see file 'COPYING' for use and warranty information
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from ipsilon.util.log import Log
from ipsilon.util.page import Page
from ipsilon.util.user import UserSession
-from ipsilon.util.plugin import PluginLoader, PluginObject
-from ipsilon.util.plugin import PluginInstaller
+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(PluginObject, Log):
+class LoginManagerBase(PluginConfig, PluginObject):
- def __init__(self):
- super(LoginManagerBase, self).__init__()
+ def __init__(self, *args):
+ PluginConfig.__init__(self)
+ PluginObject.__init__(self, *args)
+ self._root = None
+ self._site = None
self.path = '/'
- self.next_login = None
self.info = None
- def redirect_to_path(self, path):
+ def redirect_to_path(self, path, trans=None):
base = cherrypy.config.get('base.mount', "")
- raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
+ url = '%s/login/%s' % (base, path)
+ if trans:
+ url += '?%s' % trans.get_GET_arg()
+ raise cherrypy.HTTPRedirect(url)
- def auth_successful(self, username, auth_type=None, userdata=None):
- # save ref before calling UserSession login() as it
- # may regenerate the session
+ def auth_successful(self, trans, username, auth_type=None, userdata=None):
session = UserSession()
- ref = session.get_data('login', 'Return')
- if not ref:
- ref = cherrypy.config.get('base.mount', "") + '/'
+ # merge attributes from login plugin and info plugin
if self.info:
- userattrs = self.info.get_user_attrs(username)
- if userdata:
- userdata.update(userattrs or {})
- else:
- userdata = userattrs
- self.debug("User %s attributes: %s" % (username, repr(userdata)))
+ infoattrs = self.info.get_user_attrs(username)
+ else:
+ infoattrs = dict()
+
+ if userdata is None:
+ userdata = dict()
+
+ if '_groups' in infoattrs:
+ userdata['_groups'] = list(set(userdata.get('_groups', []) +
+ infoattrs['_groups']))
+ del infoattrs['_groups']
+
+ if '_extras' in infoattrs:
+ userdata['_extras'] = userdata.get('_extras', {})
+ userdata['_extras'].update(infoattrs['_extras'])
+ del infoattrs['_extras']
+
+ userdata.update(infoattrs)
+
+ self.debug("User %s attributes: %s" % (username, repr(userdata)))
if auth_type:
if userdata:
- userdata.update({'auth_type': auth_type})
+ userdata.update({'_auth_type': auth_type})
else:
- userdata = {'auth_type': auth_type}
+ 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':
- cherrypy.response.cookie[USERNAME_COOKIE] = username
- cherrypy.response.cookie[USERNAME_COOKIE]['path'] = \
- cherrypy.config.get('base.mount', '/')
- cherrypy.response.cookie[USERNAME_COOKIE]['secure'] = True
- cherrypy.response.cookie[USERNAME_COOKIE]['httponly'] = True
+ cookie = SecureCookie(USERNAME_COOKIE, username)
# 15 days
- cherrypy.response.cookie[USERNAME_COOKIE]['max-age'] = 1296000
-
- raise cherrypy.HTTPRedirect(ref)
-
- def auth_failed(self):
+ 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)
+ next_login = self.next_login()
+ if next_login:
+ return self.redirect_to_path(next_login.path, trans)
# return to the caller if any
session = UserSession()
- ref = session.get_data('login', 'Return')
- # otherwise destroy session and return error
- if not ref:
+ 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(ref)
+ raise cherrypy.HTTPRedirect(transdata['login_return'])
+
+ def set_auth_error(self):
+ cherrypy.response.status = 401
def get_tree(self, site):
raise NotImplementedError
- def enable(self, site):
- plugins = site[FACILITY]
- if self in plugins['enabled']:
- return
+ def register(self, root, site):
+ self._root = root
+ self._site = site
+
+ def next_login(self):
+ plugins = self._site[FACILITY]
+ try:
+ idx = plugins.enabled.index(self.name)
+ item = plugins.enabled[idx + 1]
+ return plugins.available[item]
+ except (ValueError, IndexError):
+ return None
- # configure self
- if self.name in plugins['config']:
- self.set_config(plugins['config'][self.name])
+ def on_enable(self):
# 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._debug('Login plugin enabled: %s' % self.name)
+ self._root.add_subtree(self.name, self.get_tree(self._site))
# Get handle of the info plugin
- self.info = root.info
-
- def disable(self, site):
- plugins = site[FACILITY]
- if self not in plugins['enabled']:
- return
-
- # 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._debug('Login plugin disabled: %s' % self.name)
+ self.info = self._root.info
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)
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()
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 = self.lm.next_login.path
-
- username = ''
- if USERNAME_COOKIE in cherrypy.request.cookie:
- username = cherrypy.request.cookie[USERNAME_COOKIE].value
+ next_login = self.lm.next_login()
+ if next_login:
+ next_url = '%s?%s' % (next_login.path,
+ self.trans.get_GET_arg())
+
+ cookie = SecureCookie(USERNAME_COOKIE)
+ cookie.receive()
+ username = cookie.value
+
+ target = None
+ if self.trans is not None:
+ tid = self.trans.transaction_id
+ target = self.trans.retrieve().get('login_target')
+ username = self.trans.retrieve().get('login_username')
+ if tid is None:
+ tid = ''
+
+ if username is None:
+ username = ''
context = {
"title": 'Login',
"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
def __init__(self, *args, **kwargs):
super(Login, self).__init__(*args, **kwargs)
- self.first_login = None
- self.info = Info()
+ self.cancel = Cancel(*args, **kwargs)
+ self.info = Info(self._site)
- loader = PluginLoader(Login, FACILITY, 'LoginManager')
- self._site[FACILITY] = loader.get_plugin_data()
- plugins = self._site[FACILITY]
+ plugins = PluginLoader(Login, FACILITY, 'LoginManager')
+ plugins.get_plugin_data()
+ self._site[FACILITY] = plugins
- available = plugins['available'].keys()
+ 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']:
+ for item in plugins.available:
+ plugin = plugins.available[item]
+ plugin.register(self, self._site)
+
+ for item in plugins.enabled:
+ self._debug('Login plugin in enabled list: %s' % item)
+ if item not in plugins.available:
continue
- plugins['available'][item].enable(self._site)
+ plugins.available[item].enable()
def add_subtree(self, name, page):
self.__dict__[name] = page
+ def get_first_login(self):
+ plugin = None
+ plugins = self._site[FACILITY]
+ if plugins.enabled:
+ first = plugins.enabled[0]
+ plugin = plugins.available[first]
+ return plugin
+
def root(self, *args, **kwargs):
- if self.first_login:
- raise cherrypy.HTTPRedirect('%s/login/%s' %
- (self.basepath,
- self.first_login.path))
+ plugin = self.get_first_login()
+ if plugin:
+ trans = self.get_valid_transaction('login', **kwargs)
+ redirect = '%s/login/%s?%s' % (self.basepath,
+ plugin.path,
+ trans.get_GET_arg())
+ raise cherrypy.HTTPRedirect(redirect)
return self._template('login/index.html', title='Login')
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 LoginManagerInstaller(object):
+ def __init__(self):
+ self.facility = FACILITY
+ self.ptype = 'login'
+ self.name = None
+
+ def unconfigure(self, opts):
+ return
+
+ def install_args(self, group):
+ raise NotImplementedError
+
+ def validate_args(self, args):
+ return
+
+ def configure(self, opts):
+ raise NotImplementedError
+
+
class LoginMgrsInstall(object):
def __init__(self):
- pi = PluginInstaller(LoginMgrsInstall)
+ pi = PluginInstaller(LoginMgrsInstall, FACILITY)
self.plugins = pi.get_plugins()