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()
48 session.login(username, userdata)
51 userattrs = self.info.get_user_attrs(username)
53 userdata.update(userattrs or {})
56 self.debug("User %s attributes: %s" % (username, repr(userdata)))
60 userdata.update({'auth_type': auth_type})
62 userdata = {'auth_type': auth_type}
64 # save username into a cookie if parent was form base auth
65 if auth_type == 'password':
66 cookie = SecureCookie(USERNAME_COOKIE, username)
68 cookie.maxage = 1296000
71 transdata = trans.retrieve()
73 redirect = transdata.get('login_return',
74 cherrypy.config.get('base.mount', "") + '/')
75 self.debug('Redirecting back to: %s' % redirect)
77 # on direct login the UI (ie not redirected by a provider) we ned to
78 # remove the transaction cookie as it won't be needed anymore
79 if trans.provider == 'login':
81 raise cherrypy.HTTPRedirect(redirect)
83 def auth_failed(self, trans):
84 # try with next module
86 return self.redirect_to_path(self.next_login.path)
88 # return to the caller if any
89 session = UserSession()
91 transdata = trans.retrieve()
93 # on direct login the UI (ie not redirected by a provider) we ned to
94 # remove the transaction cookie as it won't be needed anymore
95 if trans.provider == 'login':
98 # destroy session and return error
99 if 'login_return' not in transdata:
101 raise cherrypy.HTTPError(401)
103 raise cherrypy.HTTPRedirect(transdata['login_return'])
105 def get_tree(self, site):
106 raise NotImplementedError
108 def enable(self, site):
109 plugins = site[FACILITY]
110 if self in plugins['enabled']:
114 if self.name in plugins['config']:
115 self.set_config(plugins['config'][self.name])
117 # and add self to the root
118 root = plugins['root']
119 root.add_subtree(self.name, self.get_tree(site))
121 # finally add self in login chain
123 for prev_obj in plugins['enabled']:
124 if prev_obj.next_login:
127 while prev_obj.next_login:
128 prev_obj = prev_obj.next_login
129 prev_obj.next_login = self
130 if not root.first_login:
131 root.first_login = self
133 plugins['enabled'].append(self)
134 self._debug('Login plugin enabled: %s' % self.name)
136 # Get handle of the info plugin
137 self.info = root.info
139 def disable(self, site):
140 plugins = site[FACILITY]
141 if self not in plugins['enabled']:
144 # remove self from chain
145 root = plugins['root']
146 if root.first_login == self:
147 root.first_login = self.next_login
148 elif root.first_login:
149 prev_obj = root.first_login
150 while prev_obj.next_login != self:
151 prev_obj = prev_obj.next_login
153 prev_obj.next_login = self.next_login
154 self.next_login = None
156 plugins['enabled'].remove(self)
157 self._debug('Login plugin disabled: %s' % self.name)
160 class LoginPageBase(Page):
162 def __init__(self, site, mgr):
163 super(LoginPageBase, self).__init__(site)
165 self._Transaction = None
167 def root(self, *args, **kwargs):
168 raise cherrypy.HTTPError(500)
171 class LoginFormBase(LoginPageBase):
173 def __init__(self, site, mgr, page, template=None):
174 super(LoginFormBase, self).__init__(site, mgr)
176 self.formtemplate = template or 'login/form.html'
179 def GET(self, *args, **kwargs):
180 context = self.create_tmpl_context()
181 # pylint: disable=star-args
182 return self._template(self.formtemplate, **context)
184 def root(self, *args, **kwargs):
185 self.trans = Transaction('login', **kwargs)
186 op = getattr(self, cherrypy.request.method, self.GET)
188 return op(*args, **kwargs)
190 def create_tmpl_context(self, **kwargs):
192 if self.lm.next_login is not None:
193 next_url = '%s?%s' % (self.lm.next_login.path,
194 self.trans.get_GET_arg())
196 cookie = SecureCookie(USERNAME_COOKIE)
198 username = cookie.value
202 if self.trans is not None:
203 tid = self.trans.transaction_id
209 "action": '%s/%s' % (self.basepath, self.formpage),
210 "service_name": self.lm.service_name,
211 "username_text": self.lm.username_text,
212 "password_text": self.lm.password_text,
213 "description": self.lm.help_text,
214 "next_url": next_url,
215 "username": username,
217 context.update(kwargs)
218 if self.trans is not None:
219 t = self.trans.get_POST_tuple()
220 context.update({t[0]: t[1]})
225 FACILITY = 'login_config'
230 def __init__(self, *args, **kwargs):
231 super(Login, self).__init__(*args, **kwargs)
232 self.first_login = None
233 self.info = Info(self._site)
235 loader = PluginLoader(Login, FACILITY, 'LoginManager')
236 self._site[FACILITY] = loader.get_plugin_data()
237 plugins = self._site[FACILITY]
239 available = plugins['available'].keys()
240 self._debug('Available login managers: %s' % str(available))
242 plugins['root'] = self
243 for item in plugins['whitelist']:
244 self._debug('Login plugin in whitelist: %s' % item)
245 if item not in plugins['available']:
247 plugins['available'][item].enable(self._site)
249 def add_subtree(self, name, page):
250 self.__dict__[name] = page
252 def root(self, *args, **kwargs):
254 trans = Transaction('login', **kwargs)
255 redirect = '%s/login/%s?%s' % (self.basepath,
256 self.first_login.path,
258 raise cherrypy.HTTPRedirect(redirect)
259 return self._template('login/index.html', title='Login')
264 def root(self, *args, **kwargs):
265 UserSession().logout(self.user)
266 return self._template('logout.html', title='Logout')
269 class LoginMgrsInstall(object):
272 pi = PluginInstaller(LoginMgrsInstall)
273 self.plugins = pi.get_plugins()