Handle invalid/expired transactions gracefully
[cascardo/ipsilon.git] / ipsilon / login / authkrb.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2014  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.login.common import LoginPageBase, LoginManagerBase
21 from ipsilon.login.common import FACILITY
22 from ipsilon.util.plugin import PluginObject
23 from ipsilon.util.user import UserSession
24 from string import Template
25 import cherrypy
26 import os
27
28
29 class Krb(LoginPageBase):
30
31     def root(self, *args, **kwargs):
32         # Someone typed manually or a robot is walking th tree.
33         # Redirect to default page
34         return self.lm.redirect_to_path(self.lm.path)
35
36
37 class KrbAuth(LoginPageBase):
38
39     def root(self, *args, **kwargs):
40         trans = self.get_valid_transaction('login', **kwargs)
41         # If we can get here, we must be authenticated and remote_user
42         # was set. Check the session has a user set already or error.
43         us = UserSession()
44         us.remote_login()
45         self.user = us.get_user()
46         if not self.user.is_anonymous:
47             userdata = {'krb_principal_name': self.user.name}
48             return self.lm.auth_successful(trans, self.user.name,
49                                            'krb', userdata)
50         else:
51             return self.lm.auth_failed(trans)
52
53
54 class KrbError(LoginPageBase):
55
56     def root(self, *args, **kwargs):
57         cherrypy.log.error('REQUEST: %s' % cherrypy.request.headers)
58         # If we have no negotiate header return whatever mod_auth_kerb
59         # generated and wait for the next request
60
61         if 'WWW-Authenticate' not in cherrypy.request.headers:
62             cherrypy.response.status = 401
63
64             if self.lm.next_login:
65                 return self.lm.next_login.page.root(*args, **kwargs)
66
67             conturl = '%s/login' % self.basepath
68             return self._template('login/krb.html',
69                                   title='Kerberos Login',
70                                   cont=conturl)
71
72         # If we get here, negotiate failed
73         trans = self.get_valid_transaction('login', **kwargs)
74         return self.lm.auth_failed(trans)
75
76
77 class LoginManager(LoginManagerBase):
78
79     def __init__(self, *args, **kwargs):
80         super(LoginManager, self).__init__(*args, **kwargs)
81         self.name = 'krb'
82         self.path = 'krb/negotiate'
83         self.page = None
84         self.description = """
85 Kereros Negotiate authentication plugin. Relies on the mod_auth_kerb apache
86 plugin for actual authentication. """
87
88     def get_tree(self, site):
89         self.page = Krb(site, self)
90         self.page.__dict__['negotiate'] = KrbAuth(site, self)
91         self.page.__dict__['unauthorized'] = KrbError(site, self)
92         self.page.__dict__['failed'] = KrbError(site, self)
93         return self.page
94
95
96 CONF_TEMPLATE = """
97
98 <Location /${instance}/login/krb/negotiate>
99   AuthType Kerberos
100   AuthName "Kerberos Login"
101   KrbMethodNegotiate on
102   KrbMethodK5Passwd off
103   KrbServiceName HTTP
104   $realms
105   $keytab
106   KrbSaveCredentials off
107   KrbConstrainedDelegation off
108   # KrbLocalUserMapping On
109   Require valid-user
110
111   ErrorDocument 401 /${instance}/login/krb/unauthorized
112   ErrorDocument 500 /${instance}/login/krb/failed
113 </Location>
114 """
115
116
117 class Installer(object):
118
119     def __init__(self):
120         self.name = 'krb'
121         self.ptype = 'login'
122
123     def install_args(self, group):
124         group.add_argument('--krb', choices=['yes', 'no'], default='no',
125                            help='Configure Kerberos authentication')
126         group.add_argument('--krb-realms',
127                            help='Allowed Kerberos Auth Realms')
128         group.add_argument('--krb-httpd-keytab',
129                            default='/etc/httpd/conf/http.keytab',
130                            help='Kerberos keytab location for HTTPD')
131
132     def configure(self, opts):
133         if opts['krb'] != 'yes':
134             return
135
136         confopts = {'instance': opts['instance']}
137
138         if os.path.exists(opts['krb_httpd_keytab']):
139             confopts['keytab'] = '  Krb5KeyTab %s' % opts['krb_httpd_keytab']
140         else:
141             raise Exception('Keytab not found')
142
143         if opts['krb_realms'] is None:
144             confopts['realms'] = '  # KrbAuthRealms - Any realm is allowed'
145         else:
146             confopts['realms'] = '  KrbAuthRealms %s' % opts['krb_realms']
147
148         tmpl = Template(CONF_TEMPLATE)
149         hunk = tmpl.substitute(**confopts)  # pylint: disable=star-args
150         with open(opts['httpd_conf'], 'a') as httpd_conf:
151             httpd_conf.write(hunk)
152
153         # Add configuration data to database
154         po = PluginObject()
155         po.name = 'krb'
156         po.wipe_data()
157
158         # Update global config, put 'krb' always first
159         po.name = 'global'
160         globalconf = po.get_plugin_config(FACILITY)
161         if 'order' in globalconf:
162             order = globalconf['order'].split(',')
163         else:
164             order = []
165         order.insert(0, 'krb')
166         globalconf['order'] = ','.join(order)
167         po.set_config(globalconf)
168         po.save_plugin_config(FACILITY)