1 # Copyright (C) 2013 Simo Sorce <simo@redhat.com>
3 # see file 'COPYING' for use and warranty information
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 from ipsilon.util.page import Page
19 from ipsilon.util.user import UserSession
20 from ipsilon.util.plugin import PluginInstaller, PluginLoader
21 from ipsilon.util.plugin import PluginObject, PluginConfig
22 from ipsilon.info.common import Info
23 from ipsilon.util.cookies import SecureCookie
27 USERNAME_COOKIE = 'ipsilon_default_username'
30 class LoginManagerBase(PluginConfig, PluginObject):
32 def __init__(self, *args):
33 PluginConfig.__init__(self)
34 PluginObject.__init__(self, *args)
40 def redirect_to_path(self, path):
41 base = cherrypy.config.get('base.mount', "")
42 raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
44 def auth_successful(self, trans, username, auth_type=None, userdata=None):
45 session = UserSession()
48 userattrs = self.info.get_user_attrs(username)
50 userdata.update(userattrs.get('userdata', {}))
52 userdata = userattrs.get('userdata', {})
54 # merge groups and extras from login plugin and info plugin
55 userdata['groups'] = list(set(userdata.get('groups', []) +
56 userattrs.get('groups', [])))
58 userdata['extras'] = userdata.get('extras', {})
59 userdata['extras'].update(userattrs.get('extras', {}))
61 self.debug("User %s attributes: %s" % (username, repr(userdata)))
65 userdata.update({'auth_type': auth_type})
67 userdata = {'auth_type': auth_type}
69 # create session login including all the userdata just gathered
70 session.login(username, userdata)
72 # save username into a cookie if parent was form base auth
73 if auth_type == 'password':
74 cookie = SecureCookie(USERNAME_COOKIE, username)
76 cookie.maxage = 1296000
79 transdata = trans.retrieve()
81 redirect = transdata.get('login_return',
82 cherrypy.config.get('base.mount', "") + '/')
83 self.debug('Redirecting back to: %s' % redirect)
85 # on direct login the UI (ie not redirected by a provider) we ned to
86 # remove the transaction cookie as it won't be needed anymore
87 if trans.provider == 'login':
88 self.debug('Wiping transaction data')
90 raise cherrypy.HTTPRedirect(redirect)
92 def auth_failed(self, trans):
93 # try with next module
94 next_login = self.next_login()
96 return self.redirect_to_path(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 register(self, root, site):
122 def next_login(self):
123 plugins = self._site[FACILITY]
125 idx = plugins.enabled.index(self.name)
126 item = plugins.enabled[idx + 1]
127 return plugins.available[item]
128 except (ValueError, IndexError):
133 # and add self to the root
134 self._root.add_subtree(self.name, self.get_tree(self._site))
136 # Get handle of the info plugin
137 self.info = self._root.info
140 class LoginPageBase(Page):
142 def __init__(self, site, mgr):
143 super(LoginPageBase, self).__init__(site)
145 self._Transaction = None
147 def root(self, *args, **kwargs):
148 raise cherrypy.HTTPError(500)
151 class LoginFormBase(LoginPageBase):
153 def __init__(self, site, mgr, page, template=None):
154 super(LoginFormBase, self).__init__(site, mgr)
156 self.formtemplate = template or 'login/form.html'
159 def GET(self, *args, **kwargs):
160 context = self.create_tmpl_context()
161 # pylint: disable=star-args
162 return self._template(self.formtemplate, **context)
164 def root(self, *args, **kwargs):
165 self.trans = self.get_valid_transaction('login', **kwargs)
166 op = getattr(self, cherrypy.request.method, self.GET)
168 return op(*args, **kwargs)
170 def create_tmpl_context(self, **kwargs):
172 next_login = self.lm.next_login()
174 next_url = '%s?%s' % (next_login.path,
175 self.trans.get_GET_arg())
177 cookie = SecureCookie(USERNAME_COOKIE)
179 username = cookie.value
182 if self.trans is not None:
183 tid = self.trans.transaction_id
184 target = self.trans.retrieve().get('login_target')
185 username = self.trans.retrieve().get('login_username')
194 "action": '%s/%s' % (self.basepath, self.formpage),
195 "service_name": self.lm.service_name,
196 "username_text": self.lm.username_text,
197 "password_text": self.lm.password_text,
198 "description": self.lm.help_text,
199 "next_url": next_url,
200 "username": username,
201 "login_target": target,
202 "cancel_url": '%s/login/cancel?%s' % (self.basepath,
203 self.trans.get_GET_arg()),
205 context.update(kwargs)
206 if self.trans is not None:
207 t = self.trans.get_POST_tuple()
208 context.update({t[0]: t[1]})
213 FACILITY = 'login_config'
218 def __init__(self, *args, **kwargs):
219 super(Login, self).__init__(*args, **kwargs)
220 self.cancel = Cancel(*args, **kwargs)
221 self.info = Info(self._site)
223 plugins = PluginLoader(Login, FACILITY, 'LoginManager')
224 plugins.get_plugin_data()
225 self._site[FACILITY] = plugins
227 available = plugins.available.keys()
228 self._debug('Available login managers: %s' % str(available))
230 for item in plugins.available:
231 plugin = plugins.available[item]
232 plugin.register(self, self._site)
234 for item in plugins.enabled:
235 self._debug('Login plugin in enabled list: %s' % item)
236 if item not in plugins.available:
238 plugins.available[item].enable()
240 def add_subtree(self, name, page):
241 self.__dict__[name] = page
243 def get_first_login(self):
245 plugins = self._site[FACILITY]
247 first = plugins.enabled[0]
248 plugin = plugins.available[first]
251 def root(self, *args, **kwargs):
252 plugin = self.get_first_login()
254 trans = self.get_valid_transaction('login', **kwargs)
255 redirect = '%s/login/%s?%s' % (self.basepath,
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')
271 def GET(self, *args, **kwargs):
273 session = UserSession()
276 # return to the caller if any
277 transdata = self.get_valid_transaction('login', **kwargs).retrieve()
278 if 'login_return' not in transdata:
279 raise cherrypy.HTTPError(401)
280 raise cherrypy.HTTPRedirect(transdata['login_return'])
282 def root(self, *args, **kwargs):
283 op = getattr(self, cherrypy.request.method, self.GET)
285 return op(*args, **kwargs)
288 class LoginMgrsInstall(object):
291 pi = PluginInstaller(LoginMgrsInstall, FACILITY)
292 self.plugins = pi.get_plugins()