Add copyright
[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 # Copyright 2012-2013 GRNET S.A. All rights reserved.
16 #
17 # Redistribution and use in source and binary forms, with or
18 # without modification, are permitted provided that the following
19 # conditions are met:
20 #
21 #   1. Redistributions of source code must retain the above
22 #     copyright notice, this list of conditions and the following
23 #     disclaimer.
24 #
25 #   2. Redistributions in binary form must reproduce the above
26 #     copyright notice, this list of conditions and the following
27 #     disclaimer in the documentation and/or other materials
28 #     provided with the distribution.
29 #
30 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
31 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
32 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
33 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
34 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
37 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
38 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
39 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
40 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
41 # POSSIBILITY OF SUCH DAMAGE.
42 #
43 # The views and conclusions contained in the software and
44 # documentation are those of the authors and should not be
45 # interpreted as representing official policies, either expressed
46 # or implied, of GRNET S.A.
47
48
49 import json
50 import M2Crypto
51 import ast
52
53 from logging import getLogger
54 import voms_helper
55 from kamaki.clients import ClientError
56
57 LOG =  getLogger(__name__)
58
59 # Environment variable used to pass the request context
60 CONTEXT_ENV = 'snf.context'
61
62
63 SSL_CLIENT_S_DN_ENV = "SSL_CLIENT_S_DN"
64 SSL_CLIENT_CERT_ENV = "SSL_CLIENT_CERT"
65 SSL_CLIENT_CERT_CHAIN_ENV_PREFIX = "SSL_CLIENT_CERT_CHAIN_"
66
67 """Global variables that contain VOMS related paths
68 """
69 VOMS_POLICY = "/etc/snf/voms.json"
70 VOMSDIR_PATH = "/etc/grid-security/vomsdir/"
71 CA_PATH = "/etc/grid-security/certificates/"
72 VOMSAPI_LIB = "/usr/lib/libvomsapi.so.1"
73 PARAMS_ENV = 'snf_voms.params'
74
75 class VomsAuthN():
76     """Filter that checks for the SSL data in the reqest.
77
78     Sets 'ssl' in the context as a dictionary containing this data.
79     """
80     
81     def __init__(self, *args, **kwargs):
82         
83        
84         # VOMS stuff
85         try:
86             self.voms_json = json.loads(
87                 open(VOMS_POLICY).read())
88         except ValueError:
89             raise ClientError(
90                 'Bad Formatted VOMS json',
91                 details='The VOMS json data was not corectly formatted in file %s' % VOMS_POLICY)
92         except:
93             raise ClientError(
94                               'No loading of VOMS json file',
95                 details='The VOMS json file located in %s was not loaded' % VOMS_POLICY)
96         
97         self._no_verify = False
98
99         #super(VomsAuthN, self).__init__(*args, **kwargs)
100
101     @staticmethod
102     def _get_cert_chain(ssl_info):
103         """Return certificate and chain from the ssl info in M2Crypto format"""
104
105         cert = M2Crypto.X509.load_cert_string(ssl_info.get("cert", ""))
106         chain = M2Crypto.X509.X509_Stack()
107         for c in ssl_info.get("chain", []):
108             aux = M2Crypto.X509.load_cert_string(c)
109             chain.push(aux)
110         return cert, chain
111
112     def _get_voms_info(self, ssl_info):
113         """Extract voms info from ssl_info and return dict with it."""
114
115         try:
116             cert, chain = self._get_cert_chain(ssl_info)
117         except M2Crypto.X509.X509Error:
118             raise ClientError(
119                               'SSL data not verified',
120                               details=CONTEXT_ENV)
121        
122         with voms_helper.VOMS(VOMSDIR_PATH,
123                               CA_PATH, VOMSAPI_LIB) as v:
124             if self._no_verify:
125                 v.set_no_verify()
126                
127             voms_data = v.retrieve(cert, chain)
128             
129             if not voms_data:
130                 raise VomsError(v.error.value)
131
132             d = {}
133             for attr in ('user', 'userca', 'server', 'serverca',
134                          'voname',  'uri', 'version', 'serial',
135                          ('not_before', 'date1'), ('not_after', 'date2')):
136                 if isinstance(attr, basestring):
137                     d[attr] = getattr(voms_data, attr)
138                 else:
139                     d[attr[0]] = getattr(voms_data, attr[1])
140
141             d["fqans"] = []
142             for fqan in iter(voms_data.fqan):
143                 if fqan is None:
144                     break
145                 d["fqans"].append(fqan)
146
147         return d
148
149     @staticmethod
150     def _split_fqan(fqan):
151         """
152         gets a fqan and returns a tuple containing
153         (vo/groups, role, capability)
154         """
155         l = fqan.split("/")
156         capability = l.pop().split("=")[-1]
157         role = l.pop().split("=")[-1]
158         vogroup = "/".join(l)
159         return (vogroup, role, capability)
160     
161     def _process_environ(self, environ):
162         
163         LOG.warning("Getting the environment parameters...")
164         # the environment variable CONTENT_LENGTH may be empty or missing
165         try:
166             request_body_size = int(environ.get('CONTENT_LENGTH', 0))
167         except (ValueError):
168             request_body_size = 0
169             raise ClientError(
170                 'Not auth method provided',
171                 details='The request body is empty, while it should contain the authentication method')
172             
173         request_body = environ['wsgi.input'].read(request_body_size)
174         
175         print request_body
176         
177         request_body = request_body.replace("true","\"true\"")
178         request_body = request_body.replace('"','\'' )  
179         
180         params_parsed = ast.literal_eval(request_body)
181         
182         
183         params = {}
184         for k, v in params_parsed.iteritems():
185             if k in ('self', 'context'):
186                 continue
187             if k.startswith('_'):
188                 continue
189             params[k] = v
190             
191         
192         environ[PARAMS_ENV] = params
193         print environ[PARAMS_ENV]
194
195     def is_applicable(self, environ):
196         """Check if the request is applicable for this handler or not"""
197         print "Checking if the request is applicable for this handler or not..."
198         self._process_environ(environ)
199         params = environ.get(PARAMS_ENV, {})
200         auth = params.get("auth", {})
201         if "voms" in auth:
202             if "true" in auth["voms"]:
203                 return True
204             else:
205                 raise ClientError(
206                 'Error in json',
207                 details='Error in JSON, voms must be set to true')
208             
209         return False
210
211
212     def authenticate(self,ssl_data):
213         
214         try:
215             voms_info = self._get_voms_info(ssl_data)
216         except VomsError as e:
217             raise e
218         user_dn = voms_info["user"]
219         user_vo = voms_info["voname"]
220         user_fqans = voms_info["fqans"] 
221         
222         return user_dn, user_vo, user_fqans 
223
224           
225     def process_request(self, environ):
226         
227         print "Inside process_Request at last!!!!"
228         if not self.is_applicable(environ):
229             return self.application
230
231         ssl_dict = {
232             "dn": environ.get(SSL_CLIENT_S_DN_ENV, None),
233             "cert": environ.get(SSL_CLIENT_CERT_ENV, None),
234             "chain": [],
235         }
236         for k, v in environ.iteritems():
237             if k.startswith(SSL_CLIENT_CERT_CHAIN_ENV_PREFIX):
238                 ssl_dict["chain"].append(v)
239
240         voms_info = self._get_voms_info(ssl_dict)
241
242         params  = environ[PARAMS_ENV]
243         
244         tenant_from_req = params["auth"].get("tenantName", None)
245         
246         print voms_info, tenant_from_req
247         user_dn = voms_info["user"]
248         user_vo = voms_info["voname"]
249         user_fqans = voms_info["fqans"] 
250         environ['REMOTE_USER'] = user_dn
251         
252         return user_dn, user_vo, user_fqans