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
186 if self.trans is not None:
187 tid = self.trans.transaction_id
188 target = self.trans.retrieve().get('login_target')
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()