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, PluginConfig
22 from ipsilon.info.common import Info
23 from ipsilon.util.cookies import SecureCookie
27 USERNAME_COOKIE = 'ipsilon_default_username'
30 class LoginManagerBase(PluginConfig, PluginObject):
32 def __init__(self, *args):
33 PluginConfig.__init__(self)
34 PluginObject.__init__(self, *args)
40 def redirect_to_path(self, path, trans=None):
41 base = cherrypy.config.get('base.mount', "")
42 url = '%s/login/%s' % (base, path)
44 url += '?%s' % trans.get_GET_arg()
45 raise cherrypy.HTTPRedirect(url)
47 def auth_successful(self, trans, username, auth_type=None, userdata=None):
48 session = UserSession()
50 # merge attributes from login plugin and info plugin
52 infoattrs = self.info.get_user_attrs(username)
59 if '_groups' in infoattrs:
60 userdata['_groups'] = list(set(userdata.get('_groups', []) +
61 infoattrs['_groups']))
62 del infoattrs['_groups']
64 if '_extras' in infoattrs:
65 userdata['_extras'] = userdata.get('_extras', {})
66 userdata['_extras'].update(infoattrs['_extras'])
67 del infoattrs['_extras']
69 userdata.update(infoattrs)
71 self.debug("User %s attributes: %s" % (username, repr(userdata)))
75 userdata.update({'_auth_type': auth_type})
77 userdata = {'_auth_type': auth_type}
79 # create session login including all the userdata just gathered
80 session.login(username, userdata)
82 # save username into a cookie if parent was form base auth
83 if auth_type == 'password':
84 cookie = SecureCookie(USERNAME_COOKIE, username)
86 cookie.maxage = 1296000
89 transdata = trans.retrieve()
91 redirect = transdata.get('login_return',
92 cherrypy.config.get('base.mount', "") + '/')
93 self.debug('Redirecting back to: %s' % redirect)
95 # on direct login the UI (ie not redirected by a provider) we ned to
96 # remove the transaction cookie as it won't be needed anymore
97 if trans.provider == 'login':
98 self.debug('Wiping transaction data')
100 raise cherrypy.HTTPRedirect(redirect)
102 def auth_failed(self, trans):
103 # try with next module
104 next_login = self.next_login()
106 return self.redirect_to_path(next_login.path, trans)
108 # return to the caller if any
109 session = UserSession()
111 transdata = trans.retrieve()
113 # on direct login the UI (ie not redirected by a provider) we ned to
114 # remove the transaction cookie as it won't be needed anymore
115 if trans.provider == 'login':
118 # destroy session and return error
119 if 'login_return' not in transdata:
121 raise cherrypy.HTTPError(401)
123 raise cherrypy.HTTPRedirect(transdata['login_return'])
125 def set_auth_error(self):
126 cherrypy.response.status = 401
128 def get_tree(self, site):
129 raise NotImplementedError
131 def register(self, root, site):
135 def next_login(self):
136 plugins = self._site[FACILITY]
138 idx = plugins.enabled.index(self.name)
139 item = plugins.enabled[idx + 1]
140 return plugins.available[item]
141 except (ValueError, IndexError):
146 # and add self to the root
147 self._root.add_subtree(self.name, self.get_tree(self._site))
149 # Get handle of the info plugin
150 self.info = self._root.info
153 class LoginPageBase(Page):
155 def __init__(self, site, mgr):
156 super(LoginPageBase, self).__init__(site)
158 self._Transaction = None
160 def root(self, *args, **kwargs):
161 raise cherrypy.HTTPError(500)
164 class LoginFormBase(LoginPageBase):
166 def __init__(self, site, mgr, page, template=None):
167 super(LoginFormBase, self).__init__(site, mgr)
169 self.formtemplate = template or 'login/form.html'
172 def GET(self, *args, **kwargs):
173 context = self.create_tmpl_context()
174 # pylint: disable=star-args
175 return self._template(self.formtemplate, **context)
177 def root(self, *args, **kwargs):
178 self.trans = self.get_valid_transaction('login', **kwargs)
179 op = getattr(self, cherrypy.request.method, self.GET)
181 return op(*args, **kwargs)
183 def create_tmpl_context(self, **kwargs):
185 next_login = self.lm.next_login()
187 next_url = '%s?%s' % (next_login.path,
188 self.trans.get_GET_arg())
190 cookie = SecureCookie(USERNAME_COOKIE)
192 username = cookie.value
195 if self.trans is not None:
196 tid = self.trans.transaction_id
197 target = self.trans.retrieve().get('login_target')
198 username = self.trans.retrieve().get('login_username')
207 "action": '%s/%s' % (self.basepath, self.formpage),
208 "service_name": self.lm.service_name,
209 "username_text": self.lm.username_text,
210 "password_text": self.lm.password_text,
211 "description": self.lm.help_text,
212 "next_url": next_url,
213 "username": username,
214 "login_target": target,
215 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
216 self.trans.get_GET_arg()),
218 context.update(kwargs)
219 if self.trans is not None:
220 t = self.trans.get_POST_tuple()
221 context.update({t[0]: t[1]})
226 FACILITY = 'login_config'
231 def __init__(self, *args, **kwargs):
232 super(Login, self).__init__(*args, **kwargs)
233 self.cancel = Cancel(*args, **kwargs)
234 self.info = Info(self._site)
236 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
237 plugins.get_plugin_data()
238 self._site[FACILITY] = plugins
240 available = plugins.available.keys()
241 self._debug('Available login managers: %s' % str(available))
243 for item in plugins.available:
244 plugin = plugins.available[item]
245 plugin.register(self, self._site)
247 for item in plugins.enabled:
248 self._debug('Login plugin in enabled list: %s' % item)
249 if item not in plugins.available:
251 plugins.available[item].enable()
253 def add_subtree(self, name, page):
254 self.__dict__[name] = page
256 def get_first_login(self):
258 plugins = self._site[FACILITY]
260 first = plugins.enabled[0]
261 plugin = plugins.available[first]
264 def root(self, *args, **kwargs):
265 plugin = self.get_first_login()
267 trans = self.get_valid_transaction('login', **kwargs)
268 redirect = '%s/login/%s?%s' % (self.basepath,
271 raise cherrypy.HTTPRedirect(redirect)
272 return self._template('login/index.html', title='Login')
276 def __init__(self, *args, **kwargs):
277 super(Logout, self).__init__(*args, **kwargs)
280 def root(self, *args, **kwargs):
283 for provider in self.handlers:
284 self.debug("Calling logout for provider %s" % provider)
285 obj = self.handlers[provider]
289 return self._template('logout.html', title='Logout')
291 def add_handler(self, provider, handler):
293 Providers can register a logout handler here that is called
294 when the IdP logout link is accessed.
296 self.handlers[provider] = handler
301 def GET(self, *args, **kwargs):
303 session = UserSession()
306 # return to the caller if any
307 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
308 if 'login_return' not in transdata:
309 raise cherrypy.HTTPError(401)
310 raise cherrypy.HTTPRedirect(transdata['login_return'])
312 def root(self, *args, **kwargs):
313 op = getattr(self, cherrypy.request.method, self.GET)
315 return op(*args, **kwargs)
318 class LoginManagerInstaller(object):
320 self.facility = FACILITY
324 def unconfigure(self, opts):
327 def install_args(self, group):
328 raise NotImplementedError
330 def validate_args(self, args):
333 def configure(self, opts):
334 raise NotImplementedError
337 class LoginMgrsInstall(object):
340 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
341 self.plugins = pi.get_plugins()