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
30 USERNAME_COOKIE = 'ipsilon_default_username'
33 class LoginManagerBase(PluginObject, Log):
36 super(LoginManagerBase, self).__init__()
38 self.next_login = None
41 def redirect_to_path(self, path):
42 base = cherrypy.config.get('base.mount', "")
43 raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
45 def auth_successful(self, trans, username, auth_type=None, userdata=None):
46 session = UserSession()
49 userattrs = self.info.get_user_attrs(username)
51 userdata.update(userattrs.get('userdata', {}))
53 userdata = userattrs.get('userdata', {})
55 # merge groups and extras from login plugin and info plugin
56 userdata['groups'] = list(set(userdata.get('groups', []) +
57 userattrs.get('groups', [])))
59 userdata['extras'] = userdata.get('extras', {})
60 userdata['extras'].update(userattrs.get('extras', {}))
62 self.debug("User %s attributes: %s" % (username, repr(userdata)))
66 userdata.update({'auth_type': auth_type})
68 userdata = {'auth_type': auth_type}
70 # create session login including all the userdata just gathered
71 session.login(username, userdata)
73 # save username into a cookie if parent was form base auth
74 if auth_type == 'password':
75 cookie = SecureCookie(USERNAME_COOKIE, username)
77 cookie.maxage = 1296000
80 transdata = trans.retrieve()
82 redirect = transdata.get('login_return',
83 cherrypy.config.get('base.mount', "") + '/')
84 self.debug('Redirecting back to: %s' % redirect)
86 # on direct login the UI (ie not redirected by a provider) we ned to
87 # remove the transaction cookie as it won't be needed anymore
88 if trans.provider == 'login':
89 self.debug('Wiping transaction data')
91 raise cherrypy.HTTPRedirect(redirect)
93 def auth_failed(self, trans):
94 # try with next module
96 return self.redirect_to_path(self.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 enable(self, site):
119 plugins = site[FACILITY]
120 if self in plugins['enabled']:
124 if self.name in plugins['config']:
125 self.set_config(plugins['config'][self.name])
127 # and add self to the root
128 root = plugins['root']
129 root.add_subtree(self.name, self.get_tree(site))
131 # finally add self in login chain
133 for prev_obj in plugins['enabled']:
134 if prev_obj.next_login:
137 while prev_obj.next_login:
138 prev_obj = prev_obj.next_login
139 prev_obj.next_login = self
140 if not root.first_login:
141 root.first_login = self
143 plugins['enabled'].append(self)
144 self._debug('Login plugin enabled: %s' % self.name)
146 # Get handle of the info plugin
147 self.info = root.info
149 def disable(self, site):
150 plugins = site[FACILITY]
151 if self not in plugins['enabled']:
154 # remove self from chain
155 root = plugins['root']
156 if root.first_login == self:
157 root.first_login = self.next_login
158 elif root.first_login:
159 prev_obj = root.first_login
160 while prev_obj.next_login != self:
161 prev_obj = prev_obj.next_login
163 prev_obj.next_login = self.next_login
164 self.next_login = None
166 plugins['enabled'].remove(self)
167 self._debug('Login plugin disabled: %s' % self.name)
170 class LoginPageBase(Page):
172 def __init__(self, site, mgr):
173 super(LoginPageBase, self).__init__(site)
175 self._Transaction = None
177 def root(self, *args, **kwargs):
178 raise cherrypy.HTTPError(500)
181 class LoginFormBase(LoginPageBase):
183 def __init__(self, site, mgr, page, template=None):
184 super(LoginFormBase, self).__init__(site, mgr)
186 self.formtemplate = template or 'login/form.html'
189 def GET(self, *args, **kwargs):
190 context = self.create_tmpl_context()
191 # pylint: disable=star-args
192 return self._template(self.formtemplate, **context)
194 def root(self, *args, **kwargs):
195 self.trans = self.get_valid_transaction('login', **kwargs)
196 op = getattr(self, cherrypy.request.method, self.GET)
198 return op(*args, **kwargs)
200 def create_tmpl_context(self, **kwargs):
202 if self.lm.next_login is not None:
203 next_url = '%s?%s' % (self.lm.next_login.path,
204 self.trans.get_GET_arg())
206 cookie = SecureCookie(USERNAME_COOKIE)
208 username = cookie.value
213 if self.trans is not None:
214 tid = self.trans.transaction_id
215 target = self.trans.retrieve().get('login_target')
221 "action": '%s/%s' % (self.basepath, self.formpage),
222 "service_name": self.lm.service_name,
223 "username_text": self.lm.username_text,
224 "password_text": self.lm.password_text,
225 "description": self.lm.help_text,
226 "next_url": next_url,
227 "username": username,
228 "login_target": target,
230 context.update(kwargs)
231 if self.trans is not None:
232 t = self.trans.get_POST_tuple()
233 context.update({t[0]: t[1]})
238 FACILITY = 'login_config'
243 def __init__(self, *args, **kwargs):
244 super(Login, self).__init__(*args, **kwargs)
245 self.first_login = None
246 self.info = Info(self._site)
248 loader = PluginLoader(Login, FACILITY, 'LoginManager')
249 self._site[FACILITY] = loader.get_plugin_data()
250 plugins = self._site[FACILITY]
252 available = plugins['available'].keys()
253 self._debug('Available login managers: %s' % str(available))
255 plugins['root'] = self
256 for item in plugins['whitelist']:
257 self._debug('Login plugin in whitelist: %s' % item)
258 if item not in plugins['available']:
260 plugins['available'][item].enable(self._site)
262 def add_subtree(self, name, page):
263 self.__dict__[name] = page
265 def root(self, *args, **kwargs):
267 trans = self.get_valid_transaction('login', **kwargs)
268 redirect = '%s/login/%s?%s' % (self.basepath,
269 self.first_login.path,
271 raise cherrypy.HTTPRedirect(redirect)
272 return self._template('login/index.html', title='Login')
277 def root(self, *args, **kwargs):
278 UserSession().logout(self.user)
279 return self._template('logout.html', title='Logout')
282 class LoginMgrsInstall(object):
285 pi = PluginInstaller(LoginMgrsInstall)
286 self.plugins = pi.get_plugins()