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.page import Page
21 from ipsilon.util.user import UserSession
22 from ipsilon.util.plugin import PluginInstaller, PluginLoader
23 from ipsilon.util.plugin import PluginObject, PluginConfig
24 from ipsilon.info.common import Info
25 from ipsilon.util.cookies import SecureCookie
29 USERNAME_COOKIE = 'ipsilon_default_username'
32 class LoginManagerBase(PluginConfig, PluginObject):
35 PluginConfig.__init__(self)
36 PluginObject.__init__(self)
39 self.next_login = None
41 self.is_enabled = False
43 def redirect_to_path(self, path):
44 base = cherrypy.config.get('base.mount', "")
45 raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
47 def auth_successful(self, trans, username, auth_type=None, userdata=None):
48 session = UserSession()
51 userattrs = self.info.get_user_attrs(username)
53 userdata.update(userattrs.get('userdata', {}))
55 userdata = userattrs.get('userdata', {})
57 # merge groups and extras from login plugin and info plugin
58 userdata['groups'] = list(set(userdata.get('groups', []) +
59 userattrs.get('groups', [])))
61 userdata['extras'] = userdata.get('extras', {})
62 userdata['extras'].update(userattrs.get('extras', {}))
64 self.debug("User %s attributes: %s" % (username, repr(userdata)))
68 userdata.update({'auth_type': auth_type})
70 userdata = {'auth_type': auth_type}
72 # create session login including all the userdata just gathered
73 session.login(username, userdata)
75 # save username into a cookie if parent was form base auth
76 if auth_type == 'password':
77 cookie = SecureCookie(USERNAME_COOKIE, username)
79 cookie.maxage = 1296000
82 transdata = trans.retrieve()
84 redirect = transdata.get('login_return',
85 cherrypy.config.get('base.mount', "") + '/')
86 self.debug('Redirecting back to: %s' % redirect)
88 # on direct login the UI (ie not redirected by a provider) we ned to
89 # remove the transaction cookie as it won't be needed anymore
90 if trans.provider == 'login':
91 self.debug('Wiping transaction data')
93 raise cherrypy.HTTPRedirect(redirect)
95 def auth_failed(self, trans):
96 # try with next module
98 return self.redirect_to_path(self.next_login.path)
100 # return to the caller if any
101 session = UserSession()
103 transdata = trans.retrieve()
105 # on direct login the UI (ie not redirected by a provider) we ned to
106 # remove the transaction cookie as it won't be needed anymore
107 if trans.provider == 'login':
110 # destroy session and return error
111 if 'login_return' not in transdata:
113 raise cherrypy.HTTPError(401)
115 raise cherrypy.HTTPRedirect(transdata['login_return'])
117 def get_tree(self, site):
118 raise NotImplementedError
120 def enable(self, site):
126 plugins = self._site[FACILITY]
129 if self.name in plugins['config']:
130 self.import_config(plugins['config'][self.name])
132 # and add self to the root
133 root = plugins['root']
134 root.add_subtree(self.name, self.get_tree(site))
136 # finally add self in login chain
138 for prev_obj in plugins['enabled']:
139 if prev_obj.next_login:
142 while prev_obj.next_login:
143 prev_obj = prev_obj.next_login
144 prev_obj.next_login = self
145 if not root.first_login:
146 root.first_login = self
148 plugins['enabled'].append(self)
149 self.is_enabled = True
150 self._debug('Login plugin enabled: %s' % self.name)
152 # Get handle of the info plugin
153 self.info = root.info
155 def disable(self, site):
156 if not self.is_enabled:
159 plugins = self._site[FACILITY]
161 # remove self from chain
162 root = plugins['root']
163 if root.first_login == self:
164 root.first_login = self.next_login
165 elif root.first_login:
166 prev_obj = root.first_login
167 while prev_obj.next_login != self:
168 prev_obj = prev_obj.next_login
170 prev_obj.next_login = self.next_login
171 self.next_login = None
173 plugins['enabled'].remove(self)
174 self.is_enabled = False
175 self._debug('Login plugin disabled: %s' % self.name)
178 class LoginPageBase(Page):
180 def __init__(self, site, mgr):
181 super(LoginPageBase, self).__init__(site)
183 self._Transaction = None
185 def root(self, *args, **kwargs):
186 raise cherrypy.HTTPError(500)
189 class LoginFormBase(LoginPageBase):
191 def __init__(self, site, mgr, page, template=None):
192 super(LoginFormBase, self).__init__(site, mgr)
194 self.formtemplate = template or 'login/form.html'
197 def GET(self, *args, **kwargs):
198 context = self.create_tmpl_context()
199 # pylint: disable=star-args
200 return self._template(self.formtemplate, **context)
202 def root(self, *args, **kwargs):
203 self.trans = self.get_valid_transaction('login', **kwargs)
204 op = getattr(self, cherrypy.request.method, self.GET)
206 return op(*args, **kwargs)
208 def create_tmpl_context(self, **kwargs):
210 if self.lm.next_login is not None:
211 next_url = '%s?%s' % (self.lm.next_login.path,
212 self.trans.get_GET_arg())
214 cookie = SecureCookie(USERNAME_COOKIE)
216 username = cookie.value
221 if self.trans is not None:
222 tid = self.trans.transaction_id
223 target = self.trans.retrieve().get('login_target')
229 "action": '%s/%s' % (self.basepath, self.formpage),
230 "service_name": self.lm.service_name,
231 "username_text": self.lm.username_text,
232 "password_text": self.lm.password_text,
233 "description": self.lm.help_text,
234 "next_url": next_url,
235 "username": username,
236 "login_target": target,
237 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
238 self.trans.get_GET_arg()),
240 context.update(kwargs)
241 if self.trans is not None:
242 t = self.trans.get_POST_tuple()
243 context.update({t[0]: t[1]})
248 FACILITY = 'login_config'
253 def __init__(self, *args, **kwargs):
254 super(Login, self).__init__(*args, **kwargs)
255 self.cancel = Cancel(*args, **kwargs)
256 self.first_login = None
257 self.info = Info(self._site)
259 loader = PluginLoader(Login, FACILITY, 'LoginManager')
260 self._site[FACILITY] = loader.get_plugin_data()
261 plugins = self._site[FACILITY]
263 available = plugins['available'].keys()
264 self._debug('Available login managers: %s' % str(available))
266 plugins['root'] = self
267 for item in plugins['whitelist']:
268 self._debug('Login plugin in whitelist: %s' % item)
269 if item not in plugins['available']:
271 plugins['available'][item].enable(self._site)
273 def add_subtree(self, name, page):
274 self.__dict__[name] = page
276 def root(self, *args, **kwargs):
278 trans = self.get_valid_transaction('login', **kwargs)
279 redirect = '%s/login/%s?%s' % (self.basepath,
280 self.first_login.path,
282 raise cherrypy.HTTPRedirect(redirect)
283 return self._template('login/index.html', title='Login')
288 def root(self, *args, **kwargs):
289 UserSession().logout(self.user)
290 return self._template('logout.html', title='Logout')
295 def GET(self, *args, **kwargs):
297 session = UserSession()
300 # return to the caller if any
301 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
302 if 'login_return' not in transdata:
303 raise cherrypy.HTTPError(401)
304 raise cherrypy.HTTPRedirect(transdata['login_return'])
306 def root(self, *args, **kwargs):
307 op = getattr(self, cherrypy.request.method, self.GET)
309 return op(*args, **kwargs)
312 class LoginMgrsInstall(object):
315 pi = PluginInstaller(LoginMgrsInstall)
316 self.plugins = pi.get_plugins()