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
27 from ipsilon.util.trans import Transaction
31 USERNAME_COOKIE = 'ipsilon_default_username'
34 class LoginManagerBase(PluginObject, Log):
37 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 or {})
55 self.debug("User %s attributes: %s" % (username, repr(userdata)))
59 userdata.update({'auth_type': auth_type})
61 userdata = {'auth_type': auth_type}
63 # create session login including all the userdata just gathered
64 session.login(username, userdata)
66 # save username into a cookie if parent was form base auth
67 if auth_type == 'password':
68 cookie = SecureCookie(USERNAME_COOKIE, username)
70 cookie.maxage = 1296000
73 transdata = trans.retrieve()
75 redirect = transdata.get('login_return',
76 cherrypy.config.get('base.mount', "") + '/')
77 self.debug('Redirecting back to: %s' % redirect)
79 # on direct login the UI (ie not redirected by a provider) we ned to
80 # remove the transaction cookie as it won't be needed anymore
81 if trans.provider == 'login':
82 self.debug('Wiping transaction data')
84 raise cherrypy.HTTPRedirect(redirect)
86 def auth_failed(self, trans):
87 # try with next module
89 return self.redirect_to_path(self.next_login.path)
91 # return to the caller if any
92 session = UserSession()
94 transdata = trans.retrieve()
96 # on direct login the UI (ie not redirected by a provider) we ned to
97 # remove the transaction cookie as it won't be needed anymore
98 if trans.provider == 'login':
101 # destroy session and return error
102 if 'login_return' not in transdata:
104 raise cherrypy.HTTPError(401)
106 raise cherrypy.HTTPRedirect(transdata['login_return'])
108 def get_tree(self, site):
109 raise NotImplementedError
111 def enable(self, site):
112 plugins = site[FACILITY]
113 if self in plugins['enabled']:
117 if self.name in plugins['config']:
118 self.set_config(plugins['config'][self.name])
120 # and add self to the root
121 root = plugins['root']
122 root.add_subtree(self.name, self.get_tree(site))
124 # finally add self in login chain
126 for prev_obj in plugins['enabled']:
127 if prev_obj.next_login:
130 while prev_obj.next_login:
131 prev_obj = prev_obj.next_login
132 prev_obj.next_login = self
133 if not root.first_login:
134 root.first_login = self
136 plugins['enabled'].append(self)
137 self._debug('Login plugin enabled: %s' % self.name)
139 # Get handle of the info plugin
140 self.info = root.info
142 def disable(self, site):
143 plugins = site[FACILITY]
144 if self not in plugins['enabled']:
147 # remove self from chain
148 root = plugins['root']
149 if root.first_login == self:
150 root.first_login = self.next_login
151 elif root.first_login:
152 prev_obj = root.first_login
153 while prev_obj.next_login != self:
154 prev_obj = prev_obj.next_login
156 prev_obj.next_login = self.next_login
157 self.next_login = None
159 plugins['enabled'].remove(self)
160 self._debug('Login plugin disabled: %s' % self.name)
163 class LoginPageBase(Page):
165 def __init__(self, site, mgr):
166 super(LoginPageBase, self).__init__(site)
168 self._Transaction = None
170 def root(self, *args, **kwargs):
171 raise cherrypy.HTTPError(500)
174 class LoginFormBase(LoginPageBase):
176 def __init__(self, site, mgr, page, template=None):
177 super(LoginFormBase, self).__init__(site, mgr)
179 self.formtemplate = template or 'login/form.html'
182 def GET(self, *args, **kwargs):
183 context = self.create_tmpl_context()
184 # pylint: disable=star-args
185 return self._template(self.formtemplate, **context)
187 def root(self, *args, **kwargs):
188 self.trans = Transaction('login', **kwargs)
189 op = getattr(self, cherrypy.request.method, self.GET)
191 return op(*args, **kwargs)
193 def create_tmpl_context(self, **kwargs):
195 if self.lm.next_login is not None:
196 next_url = '%s?%s' % (self.lm.next_login.path,
197 self.trans.get_GET_arg())
199 cookie = SecureCookie(USERNAME_COOKIE)
201 username = cookie.value
205 if self.trans is not None:
206 tid = self.trans.transaction_id
212 "action": '%s/%s' % (self.basepath, self.formpage),
213 "service_name": self.lm.service_name,
214 "username_text": self.lm.username_text,
215 "password_text": self.lm.password_text,
216 "description": self.lm.help_text,
217 "next_url": next_url,
218 "username": username,
220 context.update(kwargs)
221 if self.trans is not None:
222 t = self.trans.get_POST_tuple()
223 context.update({t[0]: t[1]})
228 FACILITY = 'login_config'
233 def __init__(self, *args, **kwargs):
234 super(Login, self).__init__(*args, **kwargs)
235 self.first_login = None
236 self.info = Info(self._site)
238 loader = PluginLoader(Login, FACILITY, 'LoginManager')
239 self._site[FACILITY] = loader.get_plugin_data()
240 plugins = self._site[FACILITY]
242 available = plugins['available'].keys()
243 self._debug('Available login managers: %s' % str(available))
245 plugins['root'] = self
246 for item in plugins['whitelist']:
247 self._debug('Login plugin in whitelist: %s' % item)
248 if item not in plugins['available']:
250 plugins['available'][item].enable(self._site)
252 def add_subtree(self, name, page):
253 self.__dict__[name] = page
255 def root(self, *args, **kwargs):
257 trans = Transaction('login', **kwargs)
258 redirect = '%s/login/%s?%s' % (self.basepath,
259 self.first_login.path,
261 raise cherrypy.HTTPRedirect(redirect)
262 return self._template('login/index.html', title='Login')
267 def root(self, *args, **kwargs):
268 UserSession().logout(self.user)
269 return self._template('logout.html', title='Logout')
272 class LoginMgrsInstall(object):
275 pi = PluginInstaller(LoginMgrsInstall)
276 self.plugins = pi.get_plugins()