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
30 USERNAME_COOKIE = 'ipsilon_default_username'
33 class LoginManagerBase(PluginObject, Log):
36 super(LoginManagerBase, self).__init__()
38 self.next_login = None
41 def redirect_to_path(self, path):
42 base = cherrypy.config.get('base.mount', "")
43 raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
45 def auth_successful(self, trans, username, auth_type=None, userdata=None):
46 session = UserSession()
49 userattrs = self.info.get_user_attrs(username)
51 userdata.update(userattrs.get('userdata', {}))
53 userdata = userattrs.get('userdata', {})
55 # merge groups and extras from login plugin and info plugin
56 userdata['groups'] = list(set(userdata.get('groups', []) +
57 userattrs.get('groups', [])))
59 userdata['extras'] = userdata.get('extras', {})
60 userdata['extras'].update(userattrs.get('extras', {}))
62 self.debug("User %s attributes: %s" % (username, repr(userdata)))
66 userdata.update({'auth_type': auth_type})
68 userdata = {'auth_type': auth_type}
70 # create session login including all the userdata just gathered
71 session.login(username, userdata)
73 # save username into a cookie if parent was form base auth
74 if auth_type == 'password':
75 cookie = SecureCookie(USERNAME_COOKIE, username)
77 cookie.maxage = 1296000
80 transdata = trans.retrieve()
82 redirect = transdata.get('login_return',
83 cherrypy.config.get('base.mount', "") + '/')
84 self.debug('Redirecting back to: %s' % redirect)
86 # on direct login the UI (ie not redirected by a provider) we ned to
87 # remove the transaction cookie as it won't be needed anymore
88 if trans.provider == 'login':
89 self.debug('Wiping transaction data')
91 raise cherrypy.HTTPRedirect(redirect)
93 def auth_failed(self, trans):
94 # try with next module
96 return self.redirect_to_path(self.next_login.path)
98 # return to the caller if any
99 session = UserSession()
101 transdata = trans.retrieve()
103 # on direct login the UI (ie not redirected by a provider) we ned to
104 # remove the transaction cookie as it won't be needed anymore
105 if trans.provider == 'login':
108 # destroy session and return error
109 if 'login_return' not in transdata:
111 raise cherrypy.HTTPError(401)
113 raise cherrypy.HTTPRedirect(transdata['login_return'])
115 def get_tree(self, site):
116 raise NotImplementedError
118 def enable(self, site):
119 plugins = site[FACILITY]
120 if self in plugins['enabled']:
124 if self.name in plugins['config']:
125 self.set_config(plugins['config'][self.name])
127 # and add self to the root
128 root = plugins['root']
129 root.add_subtree(self.name, self.get_tree(site))
131 # finally add self in login chain
133 for prev_obj in plugins['enabled']:
134 if prev_obj.next_login:
137 while prev_obj.next_login:
138 prev_obj = prev_obj.next_login
139 prev_obj.next_login = self
140 if not root.first_login:
141 root.first_login = self
143 plugins['enabled'].append(self)
144 self._debug('Login plugin enabled: %s' % self.name)
146 # Get handle of the info plugin
147 self.info = root.info
149 def disable(self, site):
150 plugins = site[FACILITY]
151 if self not in plugins['enabled']:
154 # remove self from chain
155 root = plugins['root']
156 if root.first_login == self:
157 root.first_login = self.next_login
158 elif root.first_login:
159 prev_obj = root.first_login
160 while prev_obj.next_login != self:
161 prev_obj = prev_obj.next_login
163 prev_obj.next_login = self.next_login
164 self.next_login = None
166 plugins['enabled'].remove(self)
167 self._debug('Login plugin disabled: %s' % self.name)
170 class LoginPageBase(Page):
172 def __init__(self, site, mgr):
173 super(LoginPageBase, self).__init__(site)
175 self._Transaction = None
177 def root(self, *args, **kwargs):
178 raise cherrypy.HTTPError(500)
181 class LoginFormBase(LoginPageBase):
183 def __init__(self, site, mgr, page, template=None):
184 super(LoginFormBase, self).__init__(site, mgr)
186 self.formtemplate = template or 'login/form.html'
189 def GET(self, *args, **kwargs):
190 context = self.create_tmpl_context()
191 # pylint: disable=star-args
192 return self._template(self.formtemplate, **context)
194 def root(self, *args, **kwargs):
195 self.trans = self.get_valid_transaction('login', **kwargs)
196 op = getattr(self, cherrypy.request.method, self.GET)
198 return op(*args, **kwargs)
200 def create_tmpl_context(self, **kwargs):
202 if self.lm.next_login is not None:
203 next_url = '%s?%s' % (self.lm.next_login.path,
204 self.trans.get_GET_arg())
206 cookie = SecureCookie(USERNAME_COOKIE)
208 username = cookie.value
213 if self.trans is not None:
214 tid = self.trans.transaction_id
215 target = self.trans.retrieve().get('login_target')
221 "action": '%s/%s' % (self.basepath, self.formpage),
222 "service_name": self.lm.service_name,
223 "username_text": self.lm.username_text,
224 "password_text": self.lm.password_text,
225 "description": self.lm.help_text,
226 "next_url": next_url,
227 "username": username,
228 "login_target": target,
229 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
230 self.trans.get_GET_arg()),
232 context.update(kwargs)
233 if self.trans is not None:
234 t = self.trans.get_POST_tuple()
235 context.update({t[0]: t[1]})
240 FACILITY = 'login_config'
245 def __init__(self, *args, **kwargs):
246 super(Login, self).__init__(*args, **kwargs)
247 self.cancel = Cancel(*args, **kwargs)
248 self.first_login = None
249 self.info = Info(self._site)
251 loader = PluginLoader(Login, FACILITY, 'LoginManager')
252 self._site[FACILITY] = loader.get_plugin_data()
253 plugins = self._site[FACILITY]
255 available = plugins['available'].keys()
256 self._debug('Available login managers: %s' % str(available))
258 plugins['root'] = self
259 for item in plugins['whitelist']:
260 self._debug('Login plugin in whitelist: %s' % item)
261 if item not in plugins['available']:
263 plugins['available'][item].enable(self._site)
265 def add_subtree(self, name, page):
266 self.__dict__[name] = page
268 def root(self, *args, **kwargs):
270 trans = self.get_valid_transaction('login', **kwargs)
271 redirect = '%s/login/%s?%s' % (self.basepath,
272 self.first_login.path,
274 raise cherrypy.HTTPRedirect(redirect)
275 return self._template('login/index.html', title='Login')
280 def root(self, *args, **kwargs):
281 UserSession().logout(self.user)
282 return self._template('logout.html', title='Logout')
287 def GET(self, *args, **kwargs):
289 session = UserSession()
292 # return to the caller if any
293 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
294 if 'login_return' not in transdata:
295 raise cherrypy.HTTPError(401)
296 raise cherrypy.HTTPRedirect(transdata['login_return'])
298 def root(self, *args, **kwargs):
299 op = getattr(self, cherrypy.request.method, self.GET)
301 return op(*args, **kwargs)
304 class LoginMgrsInstall(object):
307 pi = PluginInstaller(LoginMgrsInstall)
308 self.plugins = pi.get_plugins()