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 get_tree(self, site):
126 raise NotImplementedError
128 def register(self, root, site):
132 def next_login(self):
133 plugins = self._site[FACILITY]
135 idx = plugins.enabled.index(self.name)
136 item = plugins.enabled[idx + 1]
137 return plugins.available[item]
138 except (ValueError, IndexError):
143 # and add self to the root
144 self._root.add_subtree(self.name, self.get_tree(self._site))
146 # Get handle of the info plugin
147 self.info = self._root.info
150 class LoginPageBase(Page):
152 def __init__(self, site, mgr):
153 super(LoginPageBase, self).__init__(site)
155 self._Transaction = None
157 def root(self, *args, **kwargs):
158 raise cherrypy.HTTPError(500)
161 class LoginFormBase(LoginPageBase):
163 def __init__(self, site, mgr, page, template=None):
164 super(LoginFormBase, self).__init__(site, mgr)
166 self.formtemplate = template or 'login/form.html'
169 def GET(self, *args, **kwargs):
170 context = self.create_tmpl_context()
171 # pylint: disable=star-args
172 return self._template(self.formtemplate, **context)
174 def root(self, *args, **kwargs):
175 self.trans = self.get_valid_transaction('login', **kwargs)
176 op = getattr(self, cherrypy.request.method, self.GET)
178 return op(*args, **kwargs)
180 def create_tmpl_context(self, **kwargs):
182 next_login = self.lm.next_login()
184 next_url = '%s?%s' % (next_login.path,
185 self.trans.get_GET_arg())
187 cookie = SecureCookie(USERNAME_COOKIE)
189 username = cookie.value
192 if self.trans is not None:
193 tid = self.trans.transaction_id
194 target = self.trans.retrieve().get('login_target')
195 username = self.trans.retrieve().get('login_username')
204 "action": '%s/%s' % (self.basepath, self.formpage),
205 "service_name": self.lm.service_name,
206 "username_text": self.lm.username_text,
207 "password_text": self.lm.password_text,
208 "description": self.lm.help_text,
209 "next_url": next_url,
210 "username": username,
211 "login_target": target,
212 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
213 self.trans.get_GET_arg()),
215 context.update(kwargs)
216 if self.trans is not None:
217 t = self.trans.get_POST_tuple()
218 context.update({t[0]: t[1]})
223 FACILITY = 'login_config'
228 def __init__(self, *args, **kwargs):
229 super(Login, self).__init__(*args, **kwargs)
230 self.cancel = Cancel(*args, **kwargs)
231 self.info = Info(self._site)
233 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
234 plugins.get_plugin_data()
235 self._site[FACILITY] = plugins
237 available = plugins.available.keys()
238 self._debug('Available login managers: %s' % str(available))
240 for item in plugins.available:
241 plugin = plugins.available[item]
242 plugin.register(self, self._site)
244 for item in plugins.enabled:
245 self._debug('Login plugin in enabled list: %s' % item)
246 if item not in plugins.available:
248 plugins.available[item].enable()
250 def add_subtree(self, name, page):
251 self.__dict__[name] = page
253 def get_first_login(self):
255 plugins = self._site[FACILITY]
257 first = plugins.enabled[0]
258 plugin = plugins.available[first]
261 def root(self, *args, **kwargs):
262 plugin = self.get_first_login()
264 trans = self.get_valid_transaction('login', **kwargs)
265 redirect = '%s/login/%s?%s' % (self.basepath,
268 raise cherrypy.HTTPRedirect(redirect)
269 return self._template('login/index.html', title='Login')
274 def root(self, *args, **kwargs):
275 UserSession().logout(self.user)
276 return self._template('logout.html', title='Logout')
281 def GET(self, *args, **kwargs):
283 session = UserSession()
286 # return to the caller if any
287 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
288 if 'login_return' not in transdata:
289 raise cherrypy.HTTPError(401)
290 raise cherrypy.HTTPRedirect(transdata['login_return'])
292 def root(self, *args, **kwargs):
293 op = getattr(self, cherrypy.request.method, self.GET)
295 return op(*args, **kwargs)
298 class LoginManagerInstaller(object):
300 self.facility = FACILITY
304 def unconfigure(self, opts):
307 def install_args(self, group):
308 raise NotImplementedError
310 def configure(self, opts):
311 raise NotImplementedError
314 class LoginMgrsInstall(object):
317 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
318 self.plugins = pi.get_plugins()