ce8213da68fe74179e42f1be93668c585bb27af3
[cascardo/ipsilon.git] / ipsilon / login / authgssapi.py
1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
2
3 from ipsilon.login.common import LoginPageBase, LoginManagerBase, \
4     LoginManagerInstaller
5 from ipsilon.util.plugin import PluginObject
6 from ipsilon.util.user import UserSession
7 from string import Template
8 import cherrypy
9 import os
10 import logging
11
12
13 class GSSAPI(LoginPageBase):
14
15     def root(self, *args, **kwargs):
16         # Someone typed manually or a robot is walking th tree.
17         # Redirect to default page
18         return self.lm.redirect_to_path(self.lm.path)
19
20
21 class GSSAPIAuth(LoginPageBase):
22
23     def root(self, *args, **kwargs):
24         trans = self.get_valid_transaction('login', **kwargs)
25         # If we can get here, we must be authenticated and remote_user
26         # was set. Check the session has a user set already or error.
27         us = UserSession()
28         us.remote_login()
29         self.user = us.get_user()
30         if not self.user.is_anonymous:
31             principal = cherrypy.request.wsgi_environ.get('GSS_NAME', None)
32             if principal:
33                 userdata = {'gssapi_principal_name': principal}
34             else:
35                 userdata = {'gssapi_principal_name': self.user.name}
36             return self.lm.auth_successful(trans, self.user.name,
37                                            'gssapi', userdata)
38         else:
39             return self.lm.auth_failed(trans)
40
41
42 class GSSAPIError(LoginPageBase):
43
44     def root(self, *args, **kwargs):
45         cherrypy.log.error('REQUEST: %s' % cherrypy.request.headers,
46                            severity=logging.DEBUG)
47         # If we have no negotiate header return whatever mod_auth_gssapi
48         # generated and wait for the next request
49
50         if 'WWW-Authenticate' not in cherrypy.request.headers:
51             cherrypy.response.status = 401
52
53             next_login = self.lm.next_login()
54             if next_login:
55                 return next_login.page.root(*args, **kwargs)
56
57             conturl = '%s/login' % self.basepath
58             return self._template('login/gssapi.html',
59                                   title='GSSAPI Login',
60                                   cont=conturl)
61
62         # If we get here, negotiate failed
63         trans = self.get_valid_transaction('login', **kwargs)
64         return self.lm.auth_failed(trans)
65
66
67 class LoginManager(LoginManagerBase):
68
69     def __init__(self, *args, **kwargs):
70         super(LoginManager, self).__init__(*args, **kwargs)
71         self.name = 'gssapi'
72         self.path = 'gssapi/negotiate'
73         self.page = None
74         self.description = """
75 GSSAPI Negotiate authentication plugin. Relies on the mod_auth_gssapi
76 apache plugin for actual authentication. """
77         self.new_config(self.name)
78
79     def get_tree(self, site):
80         self.page = GSSAPI(site, self)
81         self.page.__dict__['negotiate'] = GSSAPIAuth(site, self)
82         self.page.__dict__['unauthorized'] = GSSAPIError(site, self)
83         self.page.__dict__['failed'] = GSSAPIError(site, self)
84         return self.page
85
86
87 CONF_TEMPLATE = """
88
89 <Location /${instance}/login/gssapi/negotiate>
90   AuthType GSSAPI
91   AuthName "GSSAPI Single Sign On Login"
92   $keytab
93   GssapiSSLonly $gssapisslonly
94   GssapiLocalName on
95   Require valid-user
96
97   ErrorDocument 401 /${instance}/login/gssapi/unauthorized
98   ErrorDocument 500 /${instance}/login/gssapi/failed
99 </Location>
100 """
101
102
103 class Installer(LoginManagerInstaller):
104
105     def __init__(self, *pargs):
106         super(Installer, self).__init__()
107         self.name = 'gssapi'
108         self.pargs = pargs
109
110     def install_args(self, group):
111         group.add_argument('--gssapi', choices=['yes', 'no'], default='no',
112                            help='Configure GSSAPI authentication')
113         group.add_argument('--gssapi-httpd-keytab',
114                            default='/etc/httpd/conf/http.keytab',
115                            help='Kerberos keytab location for HTTPD')
116
117     def configure(self, opts):
118         if opts['gssapi'] != 'yes':
119             return
120
121         confopts = {'instance': opts['instance']}
122
123         if os.path.exists(opts['gssapi_httpd_keytab']):
124             confopts['keytab'] = 'GssapiCredStore keytab:%s' % (
125                 opts['gssapi_httpd_keytab'])
126         else:
127             raise Exception('Keytab not found')
128
129         if opts['secure'] == 'no':
130             confopts['gssapisslonly'] = 'Off'
131         else:
132             confopts['gssapisslonly'] = 'On'
133
134         tmpl = Template(CONF_TEMPLATE)
135         hunk = tmpl.substitute(**confopts)
136         with open(opts['httpd_conf'], 'a') as httpd_conf:
137             httpd_conf.write(hunk)
138
139         # Add configuration data to database
140         po = PluginObject(*self.pargs)
141         po.name = 'gssapi'
142         po.wipe_data()
143
144         # Update global config, put 'gssapi' always first
145         ph = self.pargs[0]
146         ph.refresh_enabled()
147         if 'gssapi' not in ph.enabled:
148             enabled = []
149             enabled.extend(ph.enabled)
150             enabled.insert(0, 'gssapi')
151             ph.save_enabled(enabled)