-#!/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
+from ipsilon.util.config import ConfigHelper
from ipsilon.info.common import Info
from ipsilon.util.cookies import SecureCookie
-from ipsilon.util.trans import Transaction
import cherrypy
USERNAME_COOKIE = 'ipsilon_default_username'
-class LoginManagerBase(PluginObject, Log):
+class LoginManagerBase(ConfigHelper, PluginObject):
- def __init__(self):
- super(LoginManagerBase, self).__init__()
+ def __init__(self, *args):
+ ConfigHelper.__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, trans, username, auth_type=None, userdata=None):
session = UserSession()
+ # merge attributes from login plugin and info plugin
if self.info:
- userattrs = self.info.get_user_attrs(username)
- if userdata:
- userdata.update(userattrs.get('userdata', {}))
- else:
- userdata = userattrs.get('userdata', {})
+ infoattrs = self.info.get_user_attrs(username)
+ else:
+ infoattrs = dict()
- # merge groups and extras from login plugin and info plugin
- userdata['groups'] = list(set(userdata.get('groups', []) +
- userattrs.get('groups', [])))
+ if userdata is None:
+ userdata = dict()
- userdata['extras'] = userdata.get('extras', {})
- userdata['extras'].update(userattrs.get('extras', {}))
+ if '_groups' in infoattrs:
+ userdata['_groups'] = list(set(userdata.get('_groups', []) +
+ infoattrs['_groups']))
+ del infoattrs['_groups']
- self.debug("User %s attributes: %s" % (username, repr(userdata)))
+ 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)
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()
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
- # configure self
- if self.name in plugins['config']:
- self.set_config(plugins['config'][self.name])
+ 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
+
+ def other_login_stacks(self):
+ plugins = self._site[FACILITY]
+ stack = list()
+ try:
+ idx = plugins.enabled.index(self.name)
+ except (ValueError, IndexError):
+ idx = None
+ for i in range(0, len(plugins.enabled)):
+ if i == idx:
+ continue
+ stack.append(plugins.available[plugins.enabled[i]])
+ return stack
+
+ 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 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 = Transaction('login', **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())
+ other_stacks = None
+ other_login_stacks = self.lm.other_login_stacks()
+ if other_login_stacks:
+ other_stacks = list()
+ for ls in other_login_stacks:
+ url = '%s?%s' % (ls.path, self.trans.get_GET_arg())
+ name = ls.name
+ other_stacks.append({'url': url, 'name': name})
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')
+ username = self.trans.retrieve().get('login_username')
if tid is None:
tid = ''
+ if username is None:
+ username = ''
+
context = {
"title": 'Login',
"action": '%s/%s' % (self.basepath, self.formpage),
"username_text": self.lm.username_text,
"password_text": self.lm.password_text,
"description": self.lm.help_text,
- "next_url": next_url,
+ "other_stacks": other_stacks,
"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:
def __init__(self, *args, **kwargs):
super(Login, self).__init__(*args, **kwargs)
- self.first_login = None
+ 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()
- self._debug('Available login managers: %s' % str(available))
+ 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:
- trans = Transaction('login', **kwargs)
+ plugin = self.get_first_login()
+ if plugin:
+ trans = self.get_valid_transaction('login', **kwargs)
redirect = '%s/login/%s?%s' % (self.basepath,
- self.first_login.path,
+ plugin.path,
trans.get_GET_arg())
raise cherrypy.HTTPRedirect(redirect)
return self._template('login/index.html', title='Login')
class Logout(Page):
+ def __init__(self, *args, **kwargs):
+ super(Logout, self).__init__(*args, **kwargs)
+ self.handlers = {}
def root(self, *args, **kwargs):
- UserSession().logout(self.user)
+ us = UserSession()
+
+ for provider in self.handlers:
+ self.debug("Calling logout for provider %s" % provider)
+ obj = self.handlers[provider]
+ obj()
+
+ us.logout(self.user)
return self._template('logout.html', title='Logout')
+ def add_handler(self, provider, handler):
+ """
+ Providers can register a logout handler here that is called
+ when the IdP logout link is accessed.
+ """
+ self.handlers[provider] = handler
+
+
+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()