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):
147 # and add self to the root
148 self._root.add_subtree(self.name, self.get_tree(self._site))
150 # Get handle of the info plugin
151 self.info = self._root.info
154 class LoginPageBase(Page):
156 def __init__(self, site, mgr):
157 super(LoginPageBase, self).__init__(site)
159 self._Transaction = None
161 def root(self, *args, **kwargs):
162 raise cherrypy.HTTPError(500)
165 class LoginFormBase(LoginPageBase):
167 def __init__(self, site, mgr, page, template=None):
168 super(LoginFormBase, self).__init__(site, mgr)
170 self.formtemplate = template or 'login/form.html'
173 def GET(self, *args, **kwargs):
174 context = self.create_tmpl_context()
175 # pylint: disable=star-args
176 return self._template(self.formtemplate, **context)
178 def root(self, *args, **kwargs):
179 self.trans = self.get_valid_transaction('login', **kwargs)
180 op = getattr(self, cherrypy.request.method, self.GET)
182 return op(*args, **kwargs)
184 def create_tmpl_context(self, **kwargs):
186 next_login = self.lm.next_login()
188 next_url = '%s?%s' % (next_login.path,
189 self.trans.get_GET_arg())
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 "next_url": next_url,
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):
328 def install_args(self, group):
329 raise NotImplementedError
331 def validate_args(self, args):
334 def configure(self, opts):
335 raise NotImplementedError
338 class LoginMgrsInstall(object):
341 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
342 self.plugins = pi.get_plugins()