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__()
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.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
120 def is_enabled(self):
122 return self in self._site[FACILITY]['enabled']
125 def enable(self, site):
127 plugins = site[FACILITY]
128 if self in plugins['enabled']:
132 if self.name in plugins['config']:
133 self.set_config(plugins['config'][self.name])
135 # and add self to the root
136 root = plugins['root']
137 root.add_subtree(self.name, self.get_tree(site))
139 # finally add self in login chain
141 for prev_obj in plugins['enabled']:
142 if prev_obj.next_login:
145 while prev_obj.next_login:
146 prev_obj = prev_obj.next_login
147 prev_obj.next_login = self
148 if not root.first_login:
149 root.first_login = self
151 plugins['enabled'].append(self)
152 self._debug('Login plugin enabled: %s' % self.name)
154 # Get handle of the info plugin
155 self.info = root.info
157 def disable(self, site):
159 plugins = site[FACILITY]
160 if self not in plugins['enabled']:
163 # remove self from chain
164 root = plugins['root']
165 if root.first_login == self:
166 root.first_login = self.next_login
167 elif root.first_login:
168 prev_obj = root.first_login
169 while prev_obj.next_login != self:
170 prev_obj = prev_obj.next_login
172 prev_obj.next_login = self.next_login
173 self.next_login = None
175 plugins['enabled'].remove(self)
176 self._debug('Login plugin disabled: %s' % self.name)
179 class LoginPageBase(Page):
181 def __init__(self, site, mgr):
182 super(LoginPageBase, self).__init__(site)
184 self._Transaction = None
186 def root(self, *args, **kwargs):
187 raise cherrypy.HTTPError(500)
190 class LoginFormBase(LoginPageBase):
192 def __init__(self, site, mgr, page, template=None):
193 super(LoginFormBase, self).__init__(site, mgr)
195 self.formtemplate = template or 'login/form.html'
198 def GET(self, *args, **kwargs):
199 context = self.create_tmpl_context()
200 # pylint: disable=star-args
201 return self._template(self.formtemplate, **context)
203 def root(self, *args, **kwargs):
204 self.trans = self.get_valid_transaction('login', **kwargs)
205 op = getattr(self, cherrypy.request.method, self.GET)
207 return op(*args, **kwargs)
209 def create_tmpl_context(self, **kwargs):
211 if self.lm.next_login is not None:
212 next_url = '%s?%s' % (self.lm.next_login.path,
213 self.trans.get_GET_arg())
215 cookie = SecureCookie(USERNAME_COOKIE)
217 username = cookie.value
222 if self.trans is not None:
223 tid = self.trans.transaction_id
224 target = self.trans.retrieve().get('login_target')
230 "action": '%s/%s' % (self.basepath, self.formpage),
231 "service_name": self.lm.service_name,
232 "username_text": self.lm.username_text,
233 "password_text": self.lm.password_text,
234 "description": self.lm.help_text,
235 "next_url": next_url,
236 "username": username,
237 "login_target": target,
238 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
239 self.trans.get_GET_arg()),
241 context.update(kwargs)
242 if self.trans is not None:
243 t = self.trans.get_POST_tuple()
244 context.update({t[0]: t[1]})
249 FACILITY = 'login_config'
254 def __init__(self, *args, **kwargs):
255 super(Login, self).__init__(*args, **kwargs)
256 self.cancel = Cancel(*args, **kwargs)
257 self.first_login = None
258 self.info = Info(self._site)
260 loader = PluginLoader(Login, FACILITY, 'LoginManager')
261 self._site[FACILITY] = loader.get_plugin_data()
262 plugins = self._site[FACILITY]
264 available = plugins['available'].keys()
265 self._debug('Available login managers: %s' % str(available))
267 plugins['root'] = self
268 for item in plugins['whitelist']:
269 self._debug('Login plugin in whitelist: %s' % item)
270 if item not in plugins['available']:
272 plugins['available'][item].enable(self._site)
274 def add_subtree(self, name, page):
275 self.__dict__[name] = page
277 def root(self, *args, **kwargs):
279 trans = self.get_valid_transaction('login', **kwargs)
280 redirect = '%s/login/%s?%s' % (self.basepath,
281 self.first_login.path,
283 raise cherrypy.HTTPRedirect(redirect)
284 return self._template('login/index.html', title='Login')
289 def root(self, *args, **kwargs):
290 UserSession().logout(self.user)
291 return self._template('logout.html', title='Logout')
296 def GET(self, *args, **kwargs):
298 session = UserSession()
301 # return to the caller if any
302 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
303 if 'login_return' not in transdata:
304 raise cherrypy.HTTPError(401)
305 raise cherrypy.HTTPRedirect(transdata['login_return'])
307 def root(self, *args, **kwargs):
308 op = getattr(self, cherrypy.request.method, self.GET)
310 return op(*args, **kwargs)
313 class LoginMgrsInstall(object):
316 pi = PluginInstaller(LoginMgrsInstall)
317 self.plugins = pi.get_plugins()