Statistics
| Branch: | Revision:

root / snfOCCI / snf_voms / __init__.py @ f25a4623

History | View | Annotate | Download (8.5 kB)

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