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
22 from ipsilon.util.config import ConfigHelper
23 from ipsilon.info.common import Info
24 from ipsilon.util.cookies import SecureCookie
28 USERNAME_COOKIE = 'ipsilon_default_username'
31 class LoginManagerBase(ConfigHelper, PluginObject):
33 def __init__(self, *args):
34 ConfigHelper.__init__(self)
35 PluginObject.__init__(self, *args)
41 def redirect_to_path(self, path, trans=None):
42 base = cherrypy.config.get('base.mount', "")
43 url = '%s/login/%s' % (base, path)
45 url += '?%s' % trans.get_GET_arg()
46 raise cherrypy.HTTPRedirect(url)
48 def auth_successful(self, trans, username, auth_type=None, userdata=None):
49 session = UserSession()
51 # merge attributes from login plugin and info plugin
53 infoattrs = self.info.get_user_attrs(username)
60 if '_groups' in infoattrs:
61 userdata['_groups'] = list(set(userdata.get('_groups', []) +
62 infoattrs['_groups']))
63 del infoattrs['_groups']
65 if '_extras' in infoattrs:
66 userdata['_extras'] = userdata.get('_extras', {})
67 userdata['_extras'].update(infoattrs['_extras'])
68 del infoattrs['_extras']
70 userdata.update(infoattrs)
72 self.debug("User %s attributes: %s" % (username, repr(userdata)))
76 userdata.update({'_auth_type': auth_type})
78 userdata = {'_auth_type': auth_type}
80 # create session login including all the userdata just gathered
81 session.login(username, userdata)
83 # save username into a cookie if parent was form base auth
84 if auth_type == 'password':
85 cookie = SecureCookie(USERNAME_COOKIE, username)
87 cookie.maxage = 1296000
90 transdata = trans.retrieve()
92 redirect = transdata.get('login_return',
93 cherrypy.config.get('base.mount', "") + '/')
94 self.debug('Redirecting back to: %s' % redirect)
96 # on direct login the UI (ie not redirected by a provider) we ned to
97 # remove the transaction cookie as it won't be needed anymore
98 if trans.provider == 'login':
99 self.debug('Wiping transaction data')
101 raise cherrypy.HTTPRedirect(redirect)
103 def auth_failed(self, trans):
104 # try with next module
105 next_login = self.next_login()
107 return self.redirect_to_path(next_login.path, trans)
109 # return to the caller if any
110 session = UserSession()
112 transdata = trans.retrieve()
114 # on direct login the UI (ie not redirected by a provider) we ned to
115 # remove the transaction cookie as it won't be needed anymore
116 if trans.provider == 'login':
119 # destroy session and return error
120 if 'login_return' not in transdata:
122 raise cherrypy.HTTPError(401)
124 raise cherrypy.HTTPRedirect(transdata['login_return'])
126 def set_auth_error(self):
127 cherrypy.response.status = 401
129 def get_tree(self, site):
130 raise NotImplementedError
132 def register(self, root, site):
136 def next_login(self):
137 plugins = self._site[FACILITY]
139 idx = plugins.enabled.index(self.name)
140 item = plugins.enabled[idx + 1]
141 return plugins.available[item]
142 except (ValueError, IndexError):
145 def other_login_stacks(self):
146 plugins = self._site[FACILITY]
149 idx = plugins.enabled.index(self.name)
150 except (ValueError, IndexError):
152 for i in range(0, len(plugins.enabled)):
155 stack.append(plugins.available[plugins.enabled[i]])
160 # and add self to the root
161 self._root.add_subtree(self.name, self.get_tree(self._site))
163 # Get handle of the info plugin
164 self.info = self._root.info
167 class LoginPageBase(Page):
169 def __init__(self, site, mgr):
170 super(LoginPageBase, self).__init__(site)
172 self._Transaction = None
174 def root(self, *args, **kwargs):
175 raise cherrypy.HTTPError(500)
178 class LoginFormBase(LoginPageBase):
180 def __init__(self, site, mgr, page, template=None):
181 super(LoginFormBase, self).__init__(site, mgr)
183 self.formtemplate = template or 'login/form.html'
186 def GET(self, *args, **kwargs):
187 context = self.create_tmpl_context()
188 return self._template(self.formtemplate, **context)
190 def root(self, *args, **kwargs):
191 self.trans = self.get_valid_transaction('login', **kwargs)
192 op = getattr(self, cherrypy.request.method, self.GET)
194 return op(*args, **kwargs)
196 def create_tmpl_context(self, **kwargs):
198 other_login_stacks = self.lm.other_login_stacks()
199 if other_login_stacks:
200 other_stacks = list()
201 for ls in other_login_stacks:
202 url = '%s?%s' % (ls.path, self.trans.get_GET_arg())
204 other_stacks.append({'url': url, 'name': name})
206 cookie = SecureCookie(USERNAME_COOKIE)
208 username = cookie.value
211 if self.trans is not None:
212 tid = self.trans.transaction_id
213 target = self.trans.retrieve().get('login_target')
214 username = self.trans.retrieve().get('login_username')
223 "action": '%s/%s' % (self.basepath, self.formpage),
224 "service_name": self.lm.service_name,
225 "username_text": self.lm.username_text,
226 "password_text": self.lm.password_text,
227 "description": self.lm.help_text,
228 "other_stacks": other_stacks,
229 "username": username,
230 "login_target": target,
231 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
232 self.trans.get_GET_arg()),
234 context.update(kwargs)
235 if self.trans is not None:
236 t = self.trans.get_POST_tuple()
237 context.update({t[0]: t[1]})
242 FACILITY = 'login_config'
247 def __init__(self, *args, **kwargs):
248 super(Login, self).__init__(*args, **kwargs)
249 self.cancel = Cancel(*args, **kwargs)
250 self.info = Info(self._site)
252 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
253 plugins.get_plugin_data()
254 self._site[FACILITY] = plugins
256 available = plugins.available.keys()
257 self.debug('Available login managers: %s' % str(available))
259 for item in plugins.available:
260 plugin = plugins.available[item]
261 plugin.register(self, self._site)
263 for item in plugins.enabled:
264 self.debug('Login plugin in enabled list: %s' % item)
265 if item not in plugins.available:
267 plugins.available[item].enable()
269 def add_subtree(self, name, page):
270 self.__dict__[name] = page
272 def get_first_login(self):
274 plugins = self._site[FACILITY]
276 first = plugins.enabled[0]
277 plugin = plugins.available[first]
280 def root(self, *args, **kwargs):
281 plugin = self.get_first_login()
283 trans = self.get_valid_transaction('login', **kwargs)
284 redirect = '%s/login/%s?%s' % (self.basepath,
287 raise cherrypy.HTTPRedirect(redirect)
288 return self._template('login/index.html', title='Login')
292 def __init__(self, *args, **kwargs):
293 super(Logout, self).__init__(*args, **kwargs)
296 def root(self, *args, **kwargs):
299 for provider in self.handlers:
300 self.debug("Calling logout for provider %s" % provider)
301 obj = self.handlers[provider]
305 return self._template('logout.html', title='Logout')
307 def add_handler(self, provider, handler):
309 Providers can register a logout handler here that is called
310 when the IdP logout link is accessed.
312 self.handlers[provider] = handler
317 def GET(self, *args, **kwargs):
319 session = UserSession()
322 # return to the caller if any
323 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
324 if 'login_return' not in transdata:
325 raise cherrypy.HTTPError(401)
326 raise cherrypy.HTTPRedirect(transdata['login_return'])
328 def root(self, *args, **kwargs):
329 op = getattr(self, cherrypy.request.method, self.GET)
331 return op(*args, **kwargs)
334 class LoginManagerInstaller(object):
336 self.facility = FACILITY
340 def unconfigure(self, opts):
343 def install_args(self, group):
344 raise NotImplementedError
346 def validate_args(self, args):
349 def configure(self, opts):
350 raise NotImplementedError
353 class LoginMgrsInstall(object):
356 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
357 self.plugins = pi.get_plugins()