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):
34 def __init__(self, *args):
35 PluginConfig.__init__(self)
36 PluginObject.__init__(self, *args)
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
96 next_login = self.next_login()
98 return self.redirect_to_path(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 register(self, root, site):
124 def next_login(self):
125 plugins = self._site[FACILITY]
127 idx = plugins.enabled.index(self.name)
128 item = plugins.enabled[idx + 1]
129 return plugins.available[item]
130 except (ValueError, IndexError):
135 # and add self to the root
136 self._root.add_subtree(self.name, self.get_tree(self._site))
138 # Get handle of the info plugin
139 self.info = self._root.info
142 class LoginPageBase(Page):
144 def __init__(self, site, mgr):
145 super(LoginPageBase, self).__init__(site)
147 self._Transaction = None
149 def root(self, *args, **kwargs):
150 raise cherrypy.HTTPError(500)
153 class LoginFormBase(LoginPageBase):
155 def __init__(self, site, mgr, page, template=None):
156 super(LoginFormBase, self).__init__(site, mgr)
158 self.formtemplate = template or 'login/form.html'
161 def GET(self, *args, **kwargs):
162 context = self.create_tmpl_context()
163 # pylint: disable=star-args
164 return self._template(self.formtemplate, **context)
166 def root(self, *args, **kwargs):
167 self.trans = self.get_valid_transaction('login', **kwargs)
168 op = getattr(self, cherrypy.request.method, self.GET)
170 return op(*args, **kwargs)
172 def create_tmpl_context(self, **kwargs):
174 next_login = self.lm.next_login()
176 next_url = '%s?%s' % (next_login.path,
177 self.trans.get_GET_arg())
179 cookie = SecureCookie(USERNAME_COOKIE)
181 username = cookie.value
184 if self.trans is not None:
185 tid = self.trans.transaction_id
186 target = self.trans.retrieve().get('login_target')
187 username = self.trans.retrieve().get('login_username')
196 "action": '%s/%s' % (self.basepath, self.formpage),
197 "service_name": self.lm.service_name,
198 "username_text": self.lm.username_text,
199 "password_text": self.lm.password_text,
200 "description": self.lm.help_text,
201 "next_url": next_url,
202 "username": username,
203 "login_target": target,
204 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
205 self.trans.get_GET_arg()),
207 context.update(kwargs)
208 if self.trans is not None:
209 t = self.trans.get_POST_tuple()
210 context.update({t[0]: t[1]})
215 FACILITY = 'login_config'
220 def __init__(self, *args, **kwargs):
221 super(Login, self).__init__(*args, **kwargs)
222 self.cancel = Cancel(*args, **kwargs)
223 self.info = Info(self._site)
225 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
226 plugins.get_plugin_data()
227 self._site[FACILITY] = plugins
229 available = plugins.available.keys()
230 self._debug('Available login managers: %s' % str(available))
232 for item in plugins.available:
233 plugin = plugins.available[item]
234 plugin.register(self, self._site)
236 for item in plugins.enabled:
237 self._debug('Login plugin in enabled list: %s' % item)
238 if item not in plugins.available:
240 plugins.available[item].enable()
242 def add_subtree(self, name, page):
243 self.__dict__[name] = page
245 def get_first_login(self):
247 plugins = self._site[FACILITY]
249 first = plugins.enabled[0]
250 plugin = plugins.available[first]
253 def root(self, *args, **kwargs):
254 plugin = self.get_first_login()
256 trans = self.get_valid_transaction('login', **kwargs)
257 redirect = '%s/login/%s?%s' % (self.basepath,
260 raise cherrypy.HTTPRedirect(redirect)
261 return self._template('login/index.html', title='Login')
266 def root(self, *args, **kwargs):
267 UserSession().logout(self.user)
268 return self._template('logout.html', title='Logout')
273 def GET(self, *args, **kwargs):
275 session = UserSession()
278 # return to the caller if any
279 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
280 if 'login_return' not in transdata:
281 raise cherrypy.HTTPError(401)
282 raise cherrypy.HTTPRedirect(transdata['login_return'])
284 def root(self, *args, **kwargs):
285 op = getattr(self, cherrypy.request.method, self.GET)
287 return op(*args, **kwargs)
290 class LoginMgrsInstall(object):
293 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
294 self.plugins = pi.get_plugins()