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.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
119 def enable(self, site):
120 plugins = site[FACILITY]
121 if self in plugins['enabled']:
125 if self.name in plugins['config']:
126 self.set_config(plugins['config'][self.name])
128 # and add self to the root
129 root = plugins['root']
130 root.add_subtree(self.name, self.get_tree(site))
132 # finally add self in login chain
134 for prev_obj in plugins['enabled']:
135 if prev_obj.next_login:
138 while prev_obj.next_login:
139 prev_obj = prev_obj.next_login
140 prev_obj.next_login = self
141 if not root.first_login:
142 root.first_login = self
144 plugins['enabled'].append(self)
145 self._debug('Login plugin enabled: %s' % self.name)
147 # Get handle of the info plugin
148 self.info = root.info
150 def disable(self, site):
151 plugins = site[FACILITY]
152 if self not in plugins['enabled']:
155 # remove self from chain
156 root = plugins['root']
157 if root.first_login == self:
158 root.first_login = self.next_login
159 elif root.first_login:
160 prev_obj = root.first_login
161 while prev_obj.next_login != self:
162 prev_obj = prev_obj.next_login
164 prev_obj.next_login = self.next_login
165 self.next_login = None
167 plugins['enabled'].remove(self)
168 self._debug('Login plugin disabled: %s' % self.name)
171 class LoginPageBase(Page):
173 def __init__(self, site, mgr):
174 super(LoginPageBase, self).__init__(site)
176 self._Transaction = None
178 def root(self, *args, **kwargs):
179 raise cherrypy.HTTPError(500)
182 class LoginFormBase(LoginPageBase):
184 def __init__(self, site, mgr, page, template=None):
185 super(LoginFormBase, self).__init__(site, mgr)
187 self.formtemplate = template or 'login/form.html'
190 def GET(self, *args, **kwargs):
191 context = self.create_tmpl_context()
192 # pylint: disable=star-args
193 return self._template(self.formtemplate, **context)
195 def root(self, *args, **kwargs):
196 self.trans = Transaction('login', **kwargs)
197 op = getattr(self, cherrypy.request.method, self.GET)
199 return op(*args, **kwargs)
201 def create_tmpl_context(self, **kwargs):
203 if self.lm.next_login is not None:
204 next_url = '%s?%s' % (self.lm.next_login.path,
205 self.trans.get_GET_arg())
207 cookie = SecureCookie(USERNAME_COOKIE)
209 username = cookie.value
213 if self.trans is not None:
214 tid = self.trans.transaction_id
220 "action": '%s/%s' % (self.basepath, self.formpage),
221 "service_name": self.lm.service_name,
222 "username_text": self.lm.username_text,
223 "password_text": self.lm.password_text,
224 "description": self.lm.help_text,
225 "next_url": next_url,
226 "username": username,
228 context.update(kwargs)
229 if self.trans is not None:
230 t = self.trans.get_POST_tuple()
231 context.update({t[0]: t[1]})
236 FACILITY = 'login_config'
241 def __init__(self, *args, **kwargs):
242 super(Login, self).__init__(*args, **kwargs)
243 self.first_login = None
244 self.info = Info(self._site)
246 loader = PluginLoader(Login, FACILITY, 'LoginManager')
247 self._site[FACILITY] = loader.get_plugin_data()
248 plugins = self._site[FACILITY]
250 available = plugins['available'].keys()
251 self._debug('Available login managers: %s' % str(available))
253 plugins['root'] = self
254 for item in plugins['whitelist']:
255 self._debug('Login plugin in whitelist: %s' % item)
256 if item not in plugins['available']:
258 plugins['available'][item].enable(self._site)
260 def add_subtree(self, name, page):
261 self.__dict__[name] = page
263 def root(self, *args, **kwargs):
265 trans = Transaction('login', **kwargs)
266 redirect = '%s/login/%s?%s' % (self.basepath,
267 self.first_login.path,
269 raise cherrypy.HTTPRedirect(redirect)
270 return self._template('login/index.html', title='Login')
275 def root(self, *args, **kwargs):
276 UserSession().logout(self.user)
277 return self._template('logout.html', title='Logout')
280 class LoginMgrsInstall(object):
283 pi = PluginInstaller(LoginMgrsInstall)
284 self.plugins = pi.get_plugins()