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
29 USERNAME_COOKIE = 'ipsilon_default_username'
32 class LoginManagerBase(PluginObject, Log):
35 super(LoginManagerBase, self).__init__()
37 self.next_login = None
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, username, auth_type=None, userdata=None):
45 # save ref before calling UserSession login() as it
46 # may regenerate the session
47 session = UserSession()
48 ref = session.get_data('login', 'Return')
50 ref = cherrypy.config.get('base.mount', "") + '/'
53 userattrs = self.info.get_user_attrs(username)
55 userdata.update(userattrs or {})
58 self.debug("User %s attributes: %s" % (username, repr(userdata)))
62 userdata.update({'auth_type': auth_type})
64 userdata = {'auth_type': auth_type}
66 session.login(username, userdata)
68 # save username into a cookie if parent was form base auth
69 if auth_type == 'password':
70 cherrypy.response.cookie[USERNAME_COOKIE] = username
71 cherrypy.response.cookie[USERNAME_COOKIE]['path'] = \
72 cherrypy.config.get('base.mount', '/')
73 cherrypy.response.cookie[USERNAME_COOKIE]['secure'] = True
74 cherrypy.response.cookie[USERNAME_COOKIE]['httponly'] = True
76 cherrypy.response.cookie[USERNAME_COOKIE]['max-age'] = 1296000
78 raise cherrypy.HTTPRedirect(ref)
80 def auth_failed(self):
81 # try with next module
83 return self.redirect_to_path(self.next_login.path)
85 # return to the caller if any
86 session = UserSession()
87 ref = session.get_data('login', 'Return')
89 # otherwise destroy session and return error
92 raise cherrypy.HTTPError(401)
94 raise cherrypy.HTTPRedirect(ref)
96 def get_tree(self, site):
97 raise NotImplementedError
99 def enable(self, site):
100 plugins = site[FACILITY]
101 if self in plugins['enabled']:
105 if self.name in plugins['config']:
106 self.set_config(plugins['config'][self.name])
108 # and add self to the root
109 root = plugins['root']
110 root.add_subtree(self.name, self.get_tree(site))
112 # finally add self in login chain
114 for prev_obj in plugins['enabled']:
115 if prev_obj.next_login:
118 while prev_obj.next_login:
119 prev_obj = prev_obj.next_login
120 prev_obj.next_login = self
121 if not root.first_login:
122 root.first_login = self
124 plugins['enabled'].append(self)
125 self._debug('Login plugin enabled: %s' % self.name)
127 # Get handle of the info plugin
128 self.info = root.info
130 def disable(self, site):
131 plugins = site[FACILITY]
132 if self not in plugins['enabled']:
135 # remove self from chain
136 root = plugins['root']
137 if root.first_login == self:
138 root.first_login = self.next_login
139 elif root.first_login:
140 prev_obj = root.first_login
141 while prev_obj.next_login != self:
142 prev_obj = prev_obj.next_login
144 prev_obj.next_login = self.next_login
145 self.next_login = None
147 plugins['enabled'].remove(self)
148 self._debug('Login plugin disabled: %s' % self.name)
151 class LoginPageBase(Page):
153 def __init__(self, site, mgr):
154 super(LoginPageBase, self).__init__(site)
157 def root(self, *args, **kwargs):
158 raise cherrypy.HTTPError(500)
161 class LoginFormBase(LoginPageBase):
163 def __init__(self, site, mgr, page, template=None):
164 super(LoginFormBase, self).__init__(site, mgr)
166 self.formtemplate = template or 'login/form.html'
168 def GET(self, *args, **kwargs):
169 context = self.create_tmpl_context()
170 # pylint: disable=star-args
171 return self._template(self.formtemplate, **context)
173 def root(self, *args, **kwargs):
174 op = getattr(self, cherrypy.request.method, self.GET)
176 return op(*args, **kwargs)
178 def create_tmpl_context(self, **kwargs):
180 if self.lm.next_login is not None:
181 next_url = self.lm.next_login.path
184 if USERNAME_COOKIE in cherrypy.request.cookie:
185 username = cherrypy.request.cookie[USERNAME_COOKIE].value
189 "action": '%s/%s' % (self.basepath, self.formpage),
190 "service_name": self.lm.service_name,
191 "username_text": self.lm.username_text,
192 "password_text": self.lm.password_text,
193 "description": self.lm.help_text,
194 "next_url": next_url,
195 "username": username,
197 context.update(kwargs)
201 FACILITY = 'login_config'
206 def __init__(self, *args, **kwargs):
207 super(Login, self).__init__(*args, **kwargs)
208 self.first_login = None
209 self.info = Info(self._site)
211 loader = PluginLoader(Login, FACILITY, 'LoginManager')
212 self._site[FACILITY] = loader.get_plugin_data()
213 plugins = self._site[FACILITY]
215 available = plugins['available'].keys()
216 self._debug('Available login managers: %s' % str(available))
218 plugins['root'] = self
219 for item in plugins['whitelist']:
220 self._debug('Login plugin in whitelist: %s' % item)
221 if item not in plugins['available']:
223 plugins['available'][item].enable(self._site)
225 def add_subtree(self, name, page):
226 self.__dict__[name] = page
228 def root(self, *args, **kwargs):
230 raise cherrypy.HTTPRedirect('%s/login/%s' %
232 self.first_login.path))
233 return self._template('login/index.html', title='Login')
238 def root(self, *args, **kwargs):
239 UserSession().logout(self.user)
240 return self._template('logout.html', title='Logout')
243 class LoginMgrsInstall(object):
246 pi = PluginInstaller(LoginMgrsInstall)
247 self.plugins = pi.get_plugins()