features for voms authentication
[snf-occi] / snfOCCI / snf_voms / __init__.py
1 # Copyright 2012 Spanish National Research Council
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
13 # under the License.
14
15 import json
16 import M2Crypto
17 import ast
18
19 from logging import getLogger
20 import voms_helper
21 from kamaki.clients import ClientError
22
23 LOG =  getLogger(__name__)
24
25 # Environment variable used to pass the request context
26 CONTEXT_ENV = 'snf.context'
27
28
29 SSL_CLIENT_S_DN_ENV = "SSL_CLIENT_S_DN"
30 SSL_CLIENT_CERT_ENV = "SSL_CLIENT_CERT"
31 SSL_CLIENT_CERT_CHAIN_ENV_PREFIX = "SSL_CLIENT_CERT_CHAIN_"
32
33 """Global variables that contain VOMS related paths
34 """
35 VOMS_POLICY = "/etc/snf/voms.json"
36 VOMSDIR_PATH = "/etc/grid-security/vomsdir/"
37 CA_PATH = "/etc/grid-security/certificates/"
38 VOMSAPI_LIB = "/usr/lib/libvomsapi.so.1"
39 PARAMS_ENV = 'snf_voms.params'
40
41 class VomsAuthN():
42     """Filter that checks for the SSL data in the reqest.
43
44     Sets 'ssl' in the context as a dictionary containing this data.
45     """
46     
47     def __init__(self, *args, **kwargs):
48         
49        
50         # VOMS stuff
51         try:
52             self.voms_json = json.loads(
53                 open(VOMS_POLICY).read())
54         except ValueError:
55             raise ClientError(
56                 'Bad Formatted VOMS json',
57                 details='The VOMS json data was not corectly formatted in file %s' % VOMS_POLICY)
58         except:
59             raise ClientError(
60                               'No loading of VOMS json file',
61                 details='The VOMS json file located in %s was not loaded' % VOMS_POLICY)
62         
63         self._no_verify = False
64
65         #super(VomsAuthN, self).__init__(*args, **kwargs)
66
67     @staticmethod
68     def _get_cert_chain(ssl_info):
69         """Return certificate and chain from the ssl info in M2Crypto format"""
70
71         cert = M2Crypto.X509.load_cert_string(ssl_info.get("cert", ""))
72         chain = M2Crypto.X509.X509_Stack()
73         for c in ssl_info.get("chain", []):
74             aux = M2Crypto.X509.load_cert_string(c)
75             chain.push(aux)
76         return cert, chain
77
78     def _get_voms_info(self, ssl_info):
79         """Extract voms info from ssl_info and return dict with it."""
80
81         try:
82             cert, chain = self._get_cert_chain(ssl_info)
83         except M2Crypto.X509.X509Error:
84             raise ClientError(
85                               'SSL data not verified',
86                               details=CONTEXT_ENV)
87        
88         with voms_helper.VOMS(VOMSDIR_PATH,
89                               CA_PATH, VOMSAPI_LIB) as v:
90             if self._no_verify:
91                 v.set_no_verify()
92                
93             voms_data = v.retrieve(cert, chain)
94             
95             if not voms_data:
96                 raise VomsError(v.error.value)
97
98             d = {}
99             for attr in ('user', 'userca', 'server', 'serverca',
100                          'voname',  'uri', 'version', 'serial',
101                          ('not_before', 'date1'), ('not_after', 'date2')):
102                 if isinstance(attr, basestring):
103                     d[attr] = getattr(voms_data, attr)
104                 else:
105                     d[attr[0]] = getattr(voms_data, attr[1])
106
107             d["fqans"] = []
108             for fqan in iter(voms_data.fqan):
109                 if fqan is None:
110                     break
111                 d["fqans"].append(fqan)
112
113         return d
114
115     @staticmethod
116     def _split_fqan(fqan):
117         """
118         gets a fqan and returns a tuple containing
119         (vo/groups, role, capability)
120         """
121         l = fqan.split("/")
122         capability = l.pop().split("=")[-1]
123         role = l.pop().split("=")[-1]
124         vogroup = "/".join(l)
125         return (vogroup, role, capability)
126     
127     def _process_environ(self, environ):
128         
129         LOG.warning("Getting the environment parameters...")
130         # the environment variable CONTENT_LENGTH may be empty or missing
131         try:
132             request_body_size = int(environ.get('CONTENT_LENGTH', 0))
133         except (ValueError):
134             request_body_size = 0
135             raise ClientError(
136                 'Not auth method provided',
137                 details='The request body is empty, while it should contain the authentication method')
138             
139         request_body = environ['wsgi.input'].read(request_body_size)
140         
141         print request_body
142         
143         request_body = request_body.replace("true","\"true\"")
144         request_body = request_body.replace('"','\'' )  
145         
146         params_parsed = ast.literal_eval(request_body)
147         
148         
149         params = {}
150         for k, v in params_parsed.iteritems():
151             if k in ('self', 'context'):
152                 continue
153             if k.startswith('_'):
154                 continue
155             params[k] = v
156             
157         
158         environ[PARAMS_ENV] = params
159         print environ[PARAMS_ENV]
160
161     def is_applicable(self, environ):
162         """Check if the request is applicable for this handler or not"""
163         print "Checking if the request is applicable for this handler or not..."
164         self._process_environ(environ)
165         params = environ.get(PARAMS_ENV, {})
166         auth = params.get("auth", {})
167         if "voms" in auth:
168             if "true" in auth["voms"]:
169                 return True
170             else:
171                 raise ClientError(
172                 'Error in json',
173                 details='Error in JSON, voms must be set to true')
174             
175         return False
176
177
178     def authenticate(self,ssl_data):
179         
180         try:
181             voms_info = self._get_voms_info(ssl_data)
182         except VomsError as e:
183             raise e
184         user_dn = voms_info["user"]
185         user_vo = voms_info["voname"]
186         user_fqans = voms_info["fqans"] 
187         
188         return user_dn, user_vo, user_fqans 
189
190           
191     def process_request(self, environ):
192         
193         print "Inside process_Request at last!!!!"
194         if not self.is_applicable(environ):
195             return self.application
196
197         ssl_dict = {
198             "dn": environ.get(SSL_CLIENT_S_DN_ENV, None),
199             "cert": environ.get(SSL_CLIENT_CERT_ENV, None),
200             "chain": [],
201         }
202         for k, v in environ.iteritems():
203             if k.startswith(SSL_CLIENT_CERT_CHAIN_ENV_PREFIX):
204                 ssl_dict["chain"].append(v)
205
206         voms_info = self._get_voms_info(ssl_dict)
207
208         params  = environ[PARAMS_ENV]
209         
210         tenant_from_req = params["auth"].get("tenantName", None)
211         
212         print voms_info, tenant_from_req
213         user_dn = voms_info["user"]
214         user_vo = voms_info["voname"]
215         user_fqans = voms_info["fqans"] 
216         environ['REMOTE_USER'] = user_dn
217         
218         return user_dn, user_vo, user_fqans