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')
277 def root(self, *args, **kwargs):
278 UserSession().logout(self.user)
279 return self._template('logout.html', title='Logout')
284 def GET(self, *args, **kwargs):
286 session = UserSession()
289 # return to the caller if any
290 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
291 if 'login_return' not in transdata:
292 raise cherrypy.HTTPError(401)
293 raise cherrypy.HTTPRedirect(transdata['login_return'])
295 def root(self, *args, **kwargs):
296 op = getattr(self, cherrypy.request.method, self.GET)
298 return op(*args, **kwargs)
301 class LoginManagerInstaller(object):
303 self.facility = FACILITY
307 def unconfigure(self, opts):
310 def install_args(self, group):
311 raise NotImplementedError
313 def validate_args(self, args):
316 def configure(self, opts):
317 raise NotImplementedError
320 class LoginMgrsInstall(object):
323 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
324 self.plugins = pi.get_plugins()