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 PluginLoader, PluginObject
23 from ipsilon.util.plugin import PluginInstaller
24 from ipsilon.info.common import Info
25 from ipsilon.util.cookies import SecureCookie
29 USERNAME_COOKIE = 'ipsilon_default_username'
32 class LoginManagerBase(PluginObject):
35 super(LoginManagerBase, self).__init__()
38 self.next_login = None
40 self.is_enabled = False
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.get('userdata', {}))
54 userdata = userattrs.get('userdata', {})
56 # merge groups and extras from login plugin and info plugin
57 userdata['groups'] = list(set(userdata.get('groups', []) +
58 userattrs.get('groups', [])))
60 userdata['extras'] = userdata.get('extras', {})
61 userdata['extras'].update(userattrs.get('extras', {}))
63 self.debug("User %s attributes: %s" % (username, repr(userdata)))
67 userdata.update({'auth_type': auth_type})
69 userdata = {'auth_type': auth_type}
71 # create session login including all the userdata just gathered
72 session.login(username, userdata)
74 # save username into a cookie if parent was form base auth
75 if auth_type == 'password':
76 cookie = SecureCookie(USERNAME_COOKIE, username)
78 cookie.maxage = 1296000
81 transdata = trans.retrieve()
83 redirect = transdata.get('login_return',
84 cherrypy.config.get('base.mount', "") + '/')
85 self.debug('Redirecting back to: %s' % redirect)
87 # on direct login the UI (ie not redirected by a provider) we ned to
88 # remove the transaction cookie as it won't be needed anymore
89 if trans.provider == 'login':
90 self.debug('Wiping transaction data')
92 raise cherrypy.HTTPRedirect(redirect)
94 def auth_failed(self, trans):
95 # try with next module
97 return self.redirect_to_path(self.next_login.path)
99 # return to the caller if any
100 session = UserSession()
102 transdata = trans.retrieve()
104 # on direct login the UI (ie not redirected by a provider) we ned to
105 # remove the transaction cookie as it won't be needed anymore
106 if trans.provider == 'login':
109 # destroy session and return error
110 if 'login_return' not in transdata:
112 raise cherrypy.HTTPError(401)
114 raise cherrypy.HTTPRedirect(transdata['login_return'])
116 def get_tree(self, site):
117 raise NotImplementedError
119 def enable(self, site):
125 plugins = self._site[FACILITY]
128 if self.name in plugins['config']:
129 self.set_config(plugins['config'][self.name])
131 # and add self to the root
132 root = plugins['root']
133 root.add_subtree(self.name, self.get_tree(site))
135 # finally add self in login chain
137 for prev_obj in plugins['enabled']:
138 if prev_obj.next_login:
141 while prev_obj.next_login:
142 prev_obj = prev_obj.next_login
143 prev_obj.next_login = self
144 if not root.first_login:
145 root.first_login = self
147 plugins['enabled'].append(self)
148 self.is_enabled = True
149 self._debug('Login plugin enabled: %s' % self.name)
151 # Get handle of the info plugin
152 self.info = root.info
154 def disable(self, site):
155 if not self.is_enabled:
158 plugins = self._site[FACILITY]
160 # remove self from chain
161 root = plugins['root']
162 if root.first_login == self:
163 root.first_login = self.next_login
164 elif root.first_login:
165 prev_obj = root.first_login
166 while prev_obj.next_login != self:
167 prev_obj = prev_obj.next_login
169 prev_obj.next_login = self.next_login
170 self.next_login = None
172 plugins['enabled'].remove(self)
173 self.is_enabled = False
174 self._debug('Login plugin disabled: %s' % self.name)
177 class LoginPageBase(Page):
179 def __init__(self, site, mgr):
180 super(LoginPageBase, self).__init__(site)
182 self._Transaction = None
184 def root(self, *args, **kwargs):
185 raise cherrypy.HTTPError(500)
188 class LoginFormBase(LoginPageBase):
190 def __init__(self, site, mgr, page, template=None):
191 super(LoginFormBase, self).__init__(site, mgr)
193 self.formtemplate = template or 'login/form.html'
196 def GET(self, *args, **kwargs):
197 context = self.create_tmpl_context()
198 # pylint: disable=star-args
199 return self._template(self.formtemplate, **context)
201 def root(self, *args, **kwargs):
202 self.trans = self.get_valid_transaction('login', **kwargs)
203 op = getattr(self, cherrypy.request.method, self.GET)
205 return op(*args, **kwargs)
207 def create_tmpl_context(self, **kwargs):
209 if self.lm.next_login is not None:
210 next_url = '%s?%s' % (self.lm.next_login.path,
211 self.trans.get_GET_arg())
213 cookie = SecureCookie(USERNAME_COOKIE)
215 username = cookie.value
220 if self.trans is not None:
221 tid = self.trans.transaction_id
222 target = self.trans.retrieve().get('login_target')
228 "action": '%s/%s' % (self.basepath, self.formpage),
229 "service_name": self.lm.service_name,
230 "username_text": self.lm.username_text,
231 "password_text": self.lm.password_text,
232 "description": self.lm.help_text,
233 "next_url": next_url,
234 "username": username,
235 "login_target": target,
236 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
237 self.trans.get_GET_arg()),
239 context.update(kwargs)
240 if self.trans is not None:
241 t = self.trans.get_POST_tuple()
242 context.update({t[0]: t[1]})
247 FACILITY = 'login_config'
252 def __init__(self, *args, **kwargs):
253 super(Login, self).__init__(*args, **kwargs)
254 self.cancel = Cancel(*args, **kwargs)
255 self.first_login = None
256 self.info = Info(self._site)
258 loader = PluginLoader(Login, FACILITY, 'LoginManager')
259 self._site[FACILITY] = loader.get_plugin_data()
260 plugins = self._site[FACILITY]
262 available = plugins['available'].keys()
263 self._debug('Available login managers: %s' % str(available))
265 plugins['root'] = self
266 for item in plugins['whitelist']:
267 self._debug('Login plugin in whitelist: %s' % item)
268 if item not in plugins['available']:
270 plugins['available'][item].enable(self._site)
272 def add_subtree(self, name, page):
273 self.__dict__[name] = page
275 def root(self, *args, **kwargs):
277 trans = self.get_valid_transaction('login', **kwargs)
278 redirect = '%s/login/%s?%s' % (self.basepath,
279 self.first_login.path,
281 raise cherrypy.HTTPRedirect(redirect)
282 return self._template('login/index.html', title='Login')
287 def root(self, *args, **kwargs):
288 UserSession().logout(self.user)
289 return self._template('logout.html', title='Logout')
294 def GET(self, *args, **kwargs):
296 session = UserSession()
299 # return to the caller if any
300 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
301 if 'login_return' not in transdata:
302 raise cherrypy.HTTPError(401)
303 raise cherrypy.HTTPRedirect(transdata['login_return'])
305 def root(self, *args, **kwargs):
306 op = getattr(self, cherrypy.request.method, self.GET)
308 return op(*args, **kwargs)
311 class LoginMgrsInstall(object):
314 pi = PluginInstaller(LoginMgrsInstall)
315 self.plugins = pi.get_plugins()