Add support for returning user attributes
[cascardo/ipsilon.git] / ipsilon / login / common.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2013  Simo Sorce <simo@redhat.com>
4 #
5 # see file 'COPYING' for use and warranty information
6 #
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.
11 #
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.
16 #
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/>.
19
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 import cherrypy
27
28
29 USERNAME_COOKIE = 'ipsilon_default_username'
30
31
32 class LoginManagerBase(PluginObject, Log):
33
34     def __init__(self):
35         super(LoginManagerBase, self).__init__()
36         self.path = '/'
37         self.next_login = None
38         self.info = None
39
40     def redirect_to_path(self, path):
41         base = cherrypy.config.get('base.mount', "")
42         raise cherrypy.HTTPRedirect('%s/login/%s' % (base, path))
43
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')
49         if not ref:
50             ref = cherrypy.config.get('base.mount', "") + '/'
51
52         if self.info:
53             userattrs = self.info.get_user_attrs(username)
54             if userdata:
55                 userdata.update(userattrs or {})
56             else:
57                 userdata = userattrs
58             self.debug("User %s attributes: %s" % (username, repr(userdata)))
59
60         if auth_type:
61             if userdata:
62                 userdata.update({'auth_type': auth_type})
63             else:
64                 userdata = {'auth_type': auth_type}
65
66         session.login(username, userdata)
67
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
75             # 15 days
76             cherrypy.response.cookie[USERNAME_COOKIE]['max-age'] = 1296000
77
78         raise cherrypy.HTTPRedirect(ref)
79
80     def auth_failed(self):
81         # try with next module
82         if self.next_login:
83             return self.redirect_to_path(self.next_login.path)
84
85         # return to the caller if any
86         session = UserSession()
87         ref = session.get_data('login', 'Return')
88
89         # otherwise destroy session and return error
90         if not ref:
91             session.logout(None)
92             raise cherrypy.HTTPError(401)
93
94         raise cherrypy.HTTPRedirect(ref)
95
96     def get_tree(self, site):
97         raise NotImplementedError
98
99     def enable(self, site):
100         plugins = site[FACILITY]
101         if self in plugins['enabled']:
102             return
103
104         # configure self
105         if self.name in plugins['config']:
106             self.set_config(plugins['config'][self.name])
107
108         # and add self to the root
109         root = plugins['root']
110         root.add_subtree(self.name, self.get_tree(site))
111
112         # finally add self in login chain
113         prev_obj = None
114         for prev_obj in plugins['enabled']:
115             if prev_obj.next_login:
116                 break
117         if prev_obj:
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
123
124         plugins['enabled'].append(self)
125         self._debug('Login plugin enabled: %s' % self.name)
126
127         # Get handle of the info plugin
128         self.info = root.info
129
130     def disable(self, site):
131         plugins = site[FACILITY]
132         if self not in plugins['enabled']:
133             return
134
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
143             if prev_obj:
144                 prev_obj.next_login = self.next_login
145         self.next_login = None
146
147         plugins['enabled'].remove(self)
148         self._debug('Login plugin disabled: %s' % self.name)
149
150
151 class LoginPageBase(Page):
152
153     def __init__(self, site, mgr):
154         super(LoginPageBase, self).__init__(site)
155         self.lm = mgr
156
157     def root(self, *args, **kwargs):
158         raise cherrypy.HTTPError(500)
159
160
161 class LoginFormBase(LoginPageBase):
162
163     def __init__(self, site, mgr, page, template=None):
164         super(LoginFormBase, self).__init__(site, mgr)
165         self.formpage = page
166         self.formtemplate = template or 'login/form.html'
167
168     def GET(self, *args, **kwargs):
169         context = self.create_tmpl_context()
170         # pylint: disable=star-args
171         return self._template(self.formtemplate, **context)
172
173     def root(self, *args, **kwargs):
174         op = getattr(self, cherrypy.request.method, self.GET)
175         if callable(op):
176             return op(*args, **kwargs)
177
178     def create_tmpl_context(self, **kwargs):
179         next_url = None
180         if self.lm.next_login is not None:
181             next_url = self.lm.next_login.path
182
183         username = ''
184         if USERNAME_COOKIE in cherrypy.request.cookie:
185             username = cherrypy.request.cookie[USERNAME_COOKIE].value
186
187         context = {
188             "title": 'Login',
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,
196         }
197         context.update(kwargs)
198         return context
199
200
201 FACILITY = 'login_config'
202
203
204 class Login(Page):
205
206     def __init__(self, *args, **kwargs):
207         super(Login, self).__init__(*args, **kwargs)
208         self.first_login = None
209         self.info = Info()
210
211         loader = PluginLoader(Login, FACILITY, 'LoginManager')
212         self._site[FACILITY] = loader.get_plugin_data()
213         plugins = self._site[FACILITY]
214
215         available = plugins['available'].keys()
216         self._debug('Available login managers: %s' % str(available))
217
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']:
222                 continue
223             plugins['available'][item].enable(self._site)
224
225     def add_subtree(self, name, page):
226         self.__dict__[name] = page
227
228     def root(self, *args, **kwargs):
229         if self.first_login:
230             raise cherrypy.HTTPRedirect('%s/login/%s' %
231                                         (self.basepath,
232                                          self.first_login.path))
233         return self._template('login/index.html', title='Login')
234
235
236 class Logout(Page):
237
238     def root(self, *args, **kwargs):
239         UserSession().logout(self.user)
240         return self._template('logout.html', title='Logout')
241
242
243 class LoginMgrsInstall(object):
244
245     def __init__(self):
246         pi = PluginInstaller(LoginMgrsInstall)
247         self.plugins = pi.get_plugins()