1 # Copyright (C) 2013 Ipsilon project Contributors, for license see COPYING
3 from ipsilon.util.page import Page
4 from ipsilon.util.user import UserSession
5 from ipsilon.util.plugin import PluginInstaller, PluginLoader
6 from ipsilon.util.plugin import PluginObject
7 from ipsilon.util.config import ConfigHelper
8 from ipsilon.info.common import Info
9 from ipsilon.util.cookies import SecureCookie
13 USERNAME_COOKIE = 'ipsilon_default_username'
16 class LoginManagerBase(ConfigHelper, PluginObject):
18 def __init__(self, *args):
19 ConfigHelper.__init__(self)
20 PluginObject.__init__(self, *args)
26 def redirect_to_path(self, path, trans=None):
27 base = cherrypy.config.get('base.mount', "")
28 url = '%s/login/%s' % (base, path)
30 url += '?%s' % trans.get_GET_arg()
31 raise cherrypy.HTTPRedirect(url)
33 def auth_successful(self, trans, username, auth_type=None, userdata=None):
34 session = UserSession()
36 # merge attributes from login plugin and info plugin
38 infoattrs = self.info.get_user_attrs(username)
45 if '_groups' in infoattrs:
46 userdata['_groups'] = list(set(userdata.get('_groups', []) +
47 infoattrs['_groups']))
48 del infoattrs['_groups']
50 if '_extras' in infoattrs:
51 userdata['_extras'] = userdata.get('_extras', {})
52 userdata['_extras'].update(infoattrs['_extras'])
53 del infoattrs['_extras']
55 userdata.update(infoattrs)
57 self.debug("User %s attributes: %s" % (username, repr(userdata)))
61 userdata.update({'_auth_type': auth_type})
63 userdata = {'_auth_type': auth_type}
65 # create session login including all the userdata just gathered
66 session.login(username, userdata)
68 # save username into a cookie if parent was form base auth
69 if auth_type == 'password':
70 cookie = SecureCookie(USERNAME_COOKIE, username)
72 cookie.maxage = 1296000
75 transdata = trans.retrieve()
77 redirect = transdata.get('login_return',
78 cherrypy.config.get('base.mount', "") + '/')
79 self.debug('Redirecting back to: %s' % redirect)
81 # on direct login the UI (ie not redirected by a provider) we ned to
82 # remove the transaction cookie as it won't be needed anymore
83 if trans.provider == 'login':
84 self.debug('Wiping transaction data')
86 raise cherrypy.HTTPRedirect(redirect)
88 def auth_failed(self, trans):
89 # try with next module
90 next_login = self.next_login()
92 return self.redirect_to_path(next_login.path, trans)
94 # return to the caller if any
95 session = UserSession()
97 transdata = trans.retrieve()
99 # on direct login the UI (ie not redirected by a provider) we ned to
100 # remove the transaction cookie as it won't be needed anymore
101 if trans.provider == 'login':
104 # destroy session and return error
105 if 'login_return' not in transdata:
107 raise cherrypy.HTTPError(401)
109 raise cherrypy.HTTPRedirect(transdata['login_return'])
111 def set_auth_error(self):
112 cherrypy.response.status = 401
114 def get_tree(self, site):
115 raise NotImplementedError
117 def register(self, root, site):
121 def next_login(self):
122 plugins = self._site[FACILITY]
124 idx = plugins.enabled.index(self.name)
125 item = plugins.enabled[idx + 1]
126 return plugins.available[item]
127 except (ValueError, IndexError):
130 def other_login_stacks(self):
131 plugins = self._site[FACILITY]
134 idx = plugins.enabled.index(self.name)
135 except (ValueError, IndexError):
137 for i in range(0, len(plugins.enabled)):
140 stack.append(plugins.available[plugins.enabled[i]])
145 # and add self to the root
146 self._root.add_subtree(self.name, self.get_tree(self._site))
148 # Get handle of the info plugin
149 self.info = self._root.info
152 class LoginPageBase(Page):
154 def __init__(self, site, mgr):
155 super(LoginPageBase, self).__init__(site)
157 self._Transaction = None
159 def root(self, *args, **kwargs):
160 raise cherrypy.HTTPError(500)
163 class LoginFormBase(LoginPageBase):
165 def __init__(self, site, mgr, page, template=None):
166 super(LoginFormBase, self).__init__(site, mgr)
168 self.formtemplate = template or 'login/form.html'
171 def GET(self, *args, **kwargs):
172 context = self.create_tmpl_context()
173 return self._template(self.formtemplate, **context)
175 def root(self, *args, **kwargs):
176 self.trans = self.get_valid_transaction('login', **kwargs)
177 op = getattr(self, cherrypy.request.method, self.GET)
179 return op(*args, **kwargs)
181 def create_tmpl_context(self, **kwargs):
183 other_login_stacks = self.lm.other_login_stacks()
184 if other_login_stacks:
185 other_stacks = list()
186 for ls in other_login_stacks:
187 url = '%s?%s' % (ls.path, self.trans.get_GET_arg())
189 other_stacks.append({'url': url, 'name': name})
191 cookie = SecureCookie(USERNAME_COOKIE)
193 username = cookie.value
196 if self.trans is not None:
197 tid = self.trans.transaction_id
198 target = self.trans.retrieve().get('login_target')
199 username = self.trans.retrieve().get('login_username')
208 "action": '%s/%s' % (self.basepath, self.formpage),
209 "service_name": self.lm.service_name,
210 "username_text": self.lm.username_text,
211 "password_text": self.lm.password_text,
212 "description": self.lm.help_text,
213 "other_stacks": other_stacks,
214 "username": username,
215 "login_target": target,
216 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
217 self.trans.get_GET_arg()),
219 context.update(kwargs)
220 if self.trans is not None:
221 t = self.trans.get_POST_tuple()
222 context.update({t[0]: t[1]})
227 FACILITY = 'login_config'
232 def __init__(self, *args, **kwargs):
233 super(Login, self).__init__(*args, **kwargs)
234 self.cancel = Cancel(*args, **kwargs)
235 self.info = Info(self._site)
237 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
238 plugins.get_plugin_data()
239 self._site[FACILITY] = plugins
241 available = plugins.available.keys()
242 self.debug('Available login managers: %s' % str(available))
244 for item in plugins.available:
245 plugin = plugins.available[item]
246 plugin.register(self, self._site)
248 for item in plugins.enabled:
249 self.debug('Login plugin in enabled list: %s' % item)
250 if item not in plugins.available:
252 plugins.available[item].enable()
254 def add_subtree(self, name, page):
255 self.__dict__[name] = page
257 def get_first_login(self):
259 plugins = self._site[FACILITY]
261 first = plugins.enabled[0]
262 plugin = plugins.available[first]
265 def root(self, *args, **kwargs):
266 plugin = self.get_first_login()
268 trans = self.get_valid_transaction('login', **kwargs)
269 redirect = '%s/login/%s?%s' % (self.basepath,
272 raise cherrypy.HTTPRedirect(redirect)
273 return self._template('login/index.html', title='Login')
277 def __init__(self, *args, **kwargs):
278 super(Logout, self).__init__(*args, **kwargs)
281 def root(self, *args, **kwargs):
284 for provider in self.handlers:
285 self.debug("Calling logout for provider %s" % provider)
286 obj = self.handlers[provider]
290 return self._template('logout.html', title='Logout')
292 def add_handler(self, provider, handler):
294 Providers can register a logout handler here that is called
295 when the IdP logout link is accessed.
297 self.handlers[provider] = handler
302 def GET(self, *args, **kwargs):
304 session = UserSession()
307 # return to the caller if any
308 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
309 if 'login_return' not in transdata:
310 raise cherrypy.HTTPError(401)
311 raise cherrypy.HTTPRedirect(transdata['login_return'])
313 def root(self, *args, **kwargs):
314 op = getattr(self, cherrypy.request.method, self.GET)
316 return op(*args, **kwargs)
319 class LoginManagerInstaller(object):
321 self.facility = FACILITY
325 def unconfigure(self, opts, changes):
328 def install_args(self, group):
329 raise NotImplementedError
331 def validate_args(self, args):
334 def configure(self, opts, changes):
335 raise NotImplementedError
338 class LoginMgrsInstall(object):
341 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
342 self.plugins = pi.get_plugins()