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 # pylint: disable=star-args
189 return self._template(self.formtemplate, **context)
191 def root(self, *args, **kwargs):
192 self.trans = self.get_valid_transaction('login', **kwargs)
193 op = getattr(self, cherrypy.request.method, self.GET)
195 return op(*args, **kwargs)
197 def create_tmpl_context(self, **kwargs):
199 other_login_stacks = self.lm.other_login_stacks()
200 if other_login_stacks:
201 other_stacks = list()
202 for ls in other_login_stacks:
203 url = '%s?%s' % (ls.path, self.trans.get_GET_arg())
205 other_stacks.append({'url': url, 'name': name})
207 cookie = SecureCookie(USERNAME_COOKIE)
209 username = cookie.value
212 if self.trans is not None:
213 tid = self.trans.transaction_id
214 target = self.trans.retrieve().get('login_target')
215 username = self.trans.retrieve().get('login_username')
224 "action": '%s/%s' % (self.basepath, self.formpage),
225 "service_name": self.lm.service_name,
226 "username_text": self.lm.username_text,
227 "password_text": self.lm.password_text,
228 "description": self.lm.help_text,
229 "other_stacks": other_stacks,
230 "username": username,
231 "login_target": target,
232 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
233 self.trans.get_GET_arg()),
235 context.update(kwargs)
236 if self.trans is not None:
237 t = self.trans.get_POST_tuple()
238 context.update({t[0]: t[1]})
243 FACILITY = 'login_config'
248 def __init__(self, *args, **kwargs):
249 super(Login, self).__init__(*args, **kwargs)
250 self.cancel = Cancel(*args, **kwargs)
251 self.info = Info(self._site)
253 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
254 plugins.get_plugin_data()
255 self._site[FACILITY] = plugins
257 available = plugins.available.keys()
258 self._debug('Available login managers: %s' % str(available))
260 for item in plugins.available:
261 plugin = plugins.available[item]
262 plugin.register(self, self._site)
264 for item in plugins.enabled:
265 self._debug('Login plugin in enabled list: %s' % item)
266 if item not in plugins.available:
268 plugins.available[item].enable()
270 def add_subtree(self, name, page):
271 self.__dict__[name] = page
273 def get_first_login(self):
275 plugins = self._site[FACILITY]
277 first = plugins.enabled[0]
278 plugin = plugins.available[first]
281 def root(self, *args, **kwargs):
282 plugin = self.get_first_login()
284 trans = self.get_valid_transaction('login', **kwargs)
285 redirect = '%s/login/%s?%s' % (self.basepath,
288 raise cherrypy.HTTPRedirect(redirect)
289 return self._template('login/index.html', title='Login')
293 def __init__(self, *args, **kwargs):
294 super(Logout, self).__init__(*args, **kwargs)
297 def root(self, *args, **kwargs):
300 for provider in self.handlers:
301 self.debug("Calling logout for provider %s" % provider)
302 obj = self.handlers[provider]
306 return self._template('logout.html', title='Logout')
308 def add_handler(self, provider, handler):
310 Providers can register a logout handler here that is called
311 when the IdP logout link is accessed.
313 self.handlers[provider] = handler
318 def GET(self, *args, **kwargs):
320 session = UserSession()
323 # return to the caller if any
324 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
325 if 'login_return' not in transdata:
326 raise cherrypy.HTTPError(401)
327 raise cherrypy.HTTPRedirect(transdata['login_return'])
329 def root(self, *args, **kwargs):
330 op = getattr(self, cherrypy.request.method, self.GET)
332 return op(*args, **kwargs)
335 class LoginManagerInstaller(object):
337 self.facility = FACILITY
341 def unconfigure(self, opts):
344 def install_args(self, group):
345 raise NotImplementedError
347 def validate_args(self, args):
350 def configure(self, opts):
351 raise NotImplementedError
354 class LoginMgrsInstall(object):
357 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
358 self.plugins = pi.get_plugins()