3 # Copyright (C) 2013 Simo Sorce <simo@redhat.com>
5 # see file 'COPYING' for use and warranty information
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from ipsilon.util.log import Log
21 from ipsilon.util.page import Page
22 from ipsilon.util.user import UserSession
23 from ipsilon.util.plugin import PluginLoader, PluginObject
24 from ipsilon.util.plugin import PluginInstaller
25 from ipsilon.info.common import Info
26 from ipsilon.util.cookies import SecureCookie
27 from ipsilon.util.trans import Transaction
31 USERNAME_COOKIE = 'ipsilon_default_username'
34 class LoginManagerBase(PluginObject, Log):
37 super(LoginManagerBase, self).__init__()
39 self.next_login = None
42 def redirect_to_path(self, path):
43 base = cherrypy.config.get('base.mount', "")
44 raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
46 def auth_successful(self, trans, username, auth_type=None, userdata=None):
47 session = UserSession()
50 userattrs = self.info.get_user_attrs(username)
52 userdata.update(userattrs or {})
55 self.debug("User %s attributes: %s" % (username, repr(userdata)))
59 userdata.update({'auth_type': auth_type})
61 userdata = {'auth_type': auth_type}
63 # create session login including all the userdata just gathered
64 session.login(username, userdata)
66 # save username into a cookie if parent was form base auth
67 if auth_type == 'password':
68 cookie = SecureCookie(USERNAME_COOKIE, username)
70 cookie.maxage = 1296000
73 transdata = trans.retrieve()
75 redirect = transdata.get('login_return',
76 cherrypy.config.get('base.mount', "") + '/')
77 self.debug('Redirecting back to: %s' % redirect)
79 # on direct login the UI (ie not redirected by a provider) we ned to
80 # remove the transaction cookie as it won't be needed anymore
81 if trans.provider == 'login':
83 raise cherrypy.HTTPRedirect(redirect)
85 def auth_failed(self, trans):
86 # try with next module
88 return self.redirect_to_path(self.next_login.path)
90 # return to the caller if any
91 session = UserSession()
93 transdata = trans.retrieve()
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':
100 # destroy session and return error
101 if 'login_return' not in transdata:
103 raise cherrypy.HTTPError(401)
105 raise cherrypy.HTTPRedirect(transdata['login_return'])
107 def get_tree(self, site):
108 raise NotImplementedError
110 def enable(self, site):
111 plugins = site[FACILITY]
112 if self in plugins['enabled']:
116 if self.name in plugins['config']:
117 self.set_config(plugins['config'][self.name])
119 # and add self to the root
120 root = plugins['root']
121 root.add_subtree(self.name, self.get_tree(site))
123 # finally add self in login chain
125 for prev_obj in plugins['enabled']:
126 if prev_obj.next_login:
129 while prev_obj.next_login:
130 prev_obj = prev_obj.next_login
131 prev_obj.next_login = self
132 if not root.first_login:
133 root.first_login = self
135 plugins['enabled'].append(self)
136 self._debug('Login plugin enabled: %s' % self.name)
138 # Get handle of the info plugin
139 self.info = root.info
141 def disable(self, site):
142 plugins = site[FACILITY]
143 if self not in plugins['enabled']:
146 # remove self from chain
147 root = plugins['root']
148 if root.first_login == self:
149 root.first_login = self.next_login
150 elif root.first_login:
151 prev_obj = root.first_login
152 while prev_obj.next_login != self:
153 prev_obj = prev_obj.next_login
155 prev_obj.next_login = self.next_login
156 self.next_login = None
158 plugins['enabled'].remove(self)
159 self._debug('Login plugin disabled: %s' % self.name)
162 class LoginPageBase(Page):
164 def __init__(self, site, mgr):
165 super(LoginPageBase, self).__init__(site)
167 self._Transaction = None
169 def root(self, *args, **kwargs):
170 raise cherrypy.HTTPError(500)
173 class LoginFormBase(LoginPageBase):
175 def __init__(self, site, mgr, page, template=None):
176 super(LoginFormBase, self).__init__(site, mgr)
178 self.formtemplate = template or 'login/form.html'
181 def GET(self, *args, **kwargs):
182 context = self.create_tmpl_context()
183 # pylint: disable=star-args
184 return self._template(self.formtemplate, **context)
186 def root(self, *args, **kwargs):
187 self.trans = Transaction('login', **kwargs)
188 op = getattr(self, cherrypy.request.method, self.GET)
190 return op(*args, **kwargs)
192 def create_tmpl_context(self, **kwargs):
194 if self.lm.next_login is not None:
195 next_url = '%s?%s' % (self.lm.next_login.path,
196 self.trans.get_GET_arg())
198 cookie = SecureCookie(USERNAME_COOKIE)
200 username = cookie.value
204 if self.trans is not None:
205 tid = self.trans.transaction_id
211 "action": '%s/%s' % (self.basepath, self.formpage),
212 "service_name": self.lm.service_name,
213 "username_text": self.lm.username_text,
214 "password_text": self.lm.password_text,
215 "description": self.lm.help_text,
216 "next_url": next_url,
217 "username": username,
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.first_login = None
235 self.info = Info(self._site)
237 loader = PluginLoader(Login, FACILITY, 'LoginManager')
238 self._site[FACILITY] = loader.get_plugin_data()
239 plugins = self._site[FACILITY]
241 available = plugins['available'].keys()
242 self._debug('Available login managers: %s' % str(available))
244 plugins['root'] = self
245 for item in plugins['whitelist']:
246 self._debug('Login plugin in whitelist: %s' % item)
247 if item not in plugins['available']:
249 plugins['available'][item].enable(self._site)
251 def add_subtree(self, name, page):
252 self.__dict__[name] = page
254 def root(self, *args, **kwargs):
256 trans = Transaction('login', **kwargs)
257 redirect = '%s/login/%s?%s' % (self.basepath,
258 self.first_login.path,
260 raise cherrypy.HTTPRedirect(redirect)
261 return self._template('login/index.html', title='Login')
266 def root(self, *args, **kwargs):
267 UserSession().logout(self.user)
268 return self._template('logout.html', title='Logout')
271 class LoginMgrsInstall(object):
274 pi = PluginInstaller(LoginMgrsInstall)
275 self.plugins = pi.get_plugins()