#!/usr/bin/env python
+import sys
+from optparse import OptionParser, OptionValueError
+import string
+import sqlite3
+import eventlet
+from eventlet import wsgi
+import os
+import json
+import uuid
+
from snfOCCI.registry import snfRegistry
from snfOCCI.compute import ComputeBackend
+from snfOCCI.config import SERVER_CONFIG, KAMAKI_CONFIG, VOMS_CONFIG
+import snf_voms
from snfOCCI.network import NetworkBackend, IpNetworkBackend, IpNetworkInterfaceBackend, NetworkInterfaceBackend
-from snfOCCI.config import SERVER_CONFIG, KAMAKI_CONFIG
from kamaki.clients.compute import ComputeClient
from kamaki.clients.cyclades import CycladesClient
+from kamaki.clients import astakos
from kamaki.clients import ClientError
+from kamaki.cli import config as kamaki_config
from occi.core_model import Mixin, Resource
-from occi.backend import MixinBackend, KindBackend
+from occi.backend import MixinBackend
from occi.extensions.infrastructure import COMPUTE, START, STOP, SUSPEND, RESTART, RESOURCE_TEMPLATE, OS_TEMPLATE, NETWORK, IPNETWORK, NETWORKINTERFACE,IPNETWORKINTERFACE
-from occi.wsgi import Application
+from occi import wsgi
from occi.exceptions import HTTPError
from occi import core_model
from wsgiref.simple_server import make_server
from wsgiref.validate import validator
-import uuid
+from webob import Request
+from pprint import pprint
+
-class MyAPP(Application):
+class MyAPP(wsgi.Application):
'''
An OCCI WSGI application.
'''
+ def __init__(self):
+ """
+ Initialization of the WSGI OCCI application for synnefo
+ """
+ global ENABLE_VOMS, VOMS_DB
+ ENABLE_VOMS = VOMS_CONFIG['enable_voms']
+ super(MyAPP,self).__init__(registry=snfRegistry())
+ self._register_backends()
+ VALIDATOR_APP = validator(self)
+
+
+ def _register_backends(self):
+ COMPUTE_BACKEND = ComputeBackend()
+ NETWORK_BACKEND = NetworkBackend()
+ NETWORKINTERFACE_BACKEND = NetworkInterfaceBackend()
+ IPNETWORK_BACKEND = IpNetworkBackend()
+ IPNETWORKINTERFACE_BACKEND = IpNetworkInterfaceBackend()
+
+ self.register_backend(COMPUTE, COMPUTE_BACKEND)
+ self.register_backend(START, COMPUTE_BACKEND)
+ self.register_backend(STOP, COMPUTE_BACKEND)
+ self.register_backend(RESTART, COMPUTE_BACKEND)
+ self.register_backend(SUSPEND, COMPUTE_BACKEND)
+ self.register_backend(RESOURCE_TEMPLATE, MixinBackend())
+ self.register_backend(OS_TEMPLATE, MixinBackend())
+
+ # Network related backends
+ self.register_backend(NETWORK, NETWORK_BACKEND)
+ self.register_backend(IPNETWORK, IPNETWORK_BACKEND)
+ self.register_backend(NETWORKINTERFACE,NETWORKINTERFACE_BACKEND)
+ self.register_backend(IPNETWORKINTERFACE, IPNETWORKINTERFACE_BACKEND)
+
+
def refresh_images(self, snf, client):
-
- images = snf.list_images()
- for image in images:
- IMAGE_ATTRIBUTES = {'occi.core.id': str(image['id'])}
- IMAGE = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(image['name']), [OS_TEMPLATE], attributes = IMAGE_ATTRIBUTES)
- self.register_backend(IMAGE, MixinBackend())
-
+ try:
+ images = snf.list_images()
+ for image in images:
+ IMAGE_ATTRIBUTES = {'occi.core.id': str(image['id'])}
+ IMAGE = Mixin("http://schemas.ogf.org/occi/os_tpl#", occify_terms(str(image['name'])), [OS_TEMPLATE],title='IMAGE' ,attributes = IMAGE_ATTRIBUTES)
+ self.register_backend(IMAGE, MixinBackend())
+ except:
+ raise HTTPError(404, "Unauthorized access")
+
def refresh_flavors(self, snf, client):
flavors = snf.list_flavors()
- print "Retrieving details for each image id"
for flavor in flavors:
details = snf.get_flavor_details(flavor['id'])
FLAVOR_ATTRIBUTES = {'occi.core.id': flavor['id'],
'occi.compute.memory': str(details['ram']),
'occi.storage.size': str(details['disk']),
}
- FLAVOR = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
+ FLAVOR = Mixin("http://schemas.ogf.org/occi/resource_tpl#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
self.register_backend(FLAVOR, MixinBackend())
+
def refresh_flavors_norecursive(self, snf, client):
flavors = snf.list_flavors(True)
print "Retrieving details for each image id"
for flavor in flavors:
- # details = snf.get_flavor_details(flavor['id'])
FLAVOR_ATTRIBUTES = {'occi.core.id': flavor['id'],
'occi.compute.cores': str(flavor['vcpus']),
'occi.compute.memory': str(flavor['ram']),
'occi.storage.size': str(flavor['disk']),
}
- FLAVOR = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
+ FLAVOR = Mixin("http://schemas.ogf.org/occi/resource_tpl#", occify_terms(str(flavor['name'])), [RESOURCE_TEMPLATE], title='FLAVOR',attributes = FLAVOR_ATTRIBUTES)
self.register_backend(FLAVOR, MixinBackend())
-
def refresh_network_instances(self,client):
networks =client.networks_get(command = 'detail')
network_details = networks.json['networks']
self.registry.add_resource(netID, snf_net, None)
-
+
def refresh_compute_instances(self, snf, client):
'''Syncing registry with cyclades resources'''
resources = self.registry.resources
occi_keys = resources.keys()
- print resources.keys()
-
+ print occi_keys
for serverID in occi_keys:
if '/compute/' in serverID and resources[serverID].attributes['occi.compute.hostname'] == "":
self.registry.delete_resource(serverID, None)
#Compute instances in synnefo not available in registry
diff = [x for x in snf_keys if '/compute/'+x not in occi_keys]
+
for key in diff:
details = snf.get_server_details(int(key))
flavor = snf.get_flavor_details(details['flavor']['id'])
try:
- print "line 65:Finished getting image details for VM with ID" + str(details['flavor']['id'])
+ print "line 65:Finished getting image details for VM "+key+" with ID" + str(details['flavor']['id'])
image = snf.get_image_details(details['image']['id'])
for i in self.registry.backends:
- if i.term == str(image['name']):
+ if i.term == occify_terms(str(image['name'])):
rel_image = i
- if i.term == str(flavor['name']):
+ if i.term == occify_terms(str(flavor['name'])):
rel_flavor = i
+
resource = Resource(key, COMPUTE, [rel_flavor, rel_image])
resource.actions = [START]
resource.attributes['occi.core.title'] = str(details['name'])
networkIDs = details['addresses'].keys()
if len(networkIDs)>0:
- #resource.attributes['occi.compute.hostname'] = SERVER_CONFIG['hostname'] % {'id':int(key)}
resource.attributes['occi.compute.hostname'] = str(details['addresses'][networkIDs[0]][0]['addr'])
- #resource.attributes['occi.networkinterface.address'] = str(details['addresses'][networkIDs[0]][0]['addr'])
else:
resource.attributes['occi.compute.hostname'] = ""
'occi.networkinterface.allocation' : allocheme,
'occi.networking.interface': str(item['id']),
'occi.networkinterface.mac' : str(item['mac_address']),
- 'occi.networkinterface.ip4' : ip4address,
+ 'occi.networkinterface.address' : ip4address,
'occi.networkinterface.ip6' : ip6address
}
elif len(details['addresses'][netKey])>0:
'occi.networkinterface.allocation' : allocheme,
'occi.networking.interface': '',
'occi.networkinterface.mac' : '',
- 'occi.networkinterface.ip4' : ip4address,
+ 'occi.networkinterface.address' : ip4address,
'occi.networkinterface.ip6' : ip6address
}
'occi.networkinterface.allocation' : '',
'occi.networking.interface': '',
'occi.networkinterface.mac' : '',
- 'occi.networkinterface.ip4' :'',
+ 'occi.networkinterface.address' :'',
'occi.networkinterface.ip6' : '' }
resource.links.append(NET_LINK)
except ClientError as ce:
if ce.status == 404:
- print('Image not found, sorry!!!')
+ print('Image not found (probably older version')
continue
else:
raise ce
if '/network/' not in key:
self.registry.delete_resource(key, None)
-
+
def __call__(self, environ, response):
+ # Enable VOMS Authorization
+ print "snf-occi application has been called!"
+
+ req = Request(environ)
+ auth_endpoint = 'snf-auth uri=\'https://'+SERVER_CONFIG['hostname']+':5000/main\''
+
+ if not req.environ.has_key('HTTP_X_AUTH_TOKEN'):
+
+ print "Error: An authentication token has not been provided!"
+ status = '401 Not Authorized'
+ headers = [('Content-Type', 'text/html'),('Www-Authenticate',auth_endpoint)]
+ response(status,headers)
+ return [str(response)]
+
+
+ if ENABLE_VOMS:
+
+ if req.environ.has_key('HTTP_X_AUTH_TOKEN'):
+
+ environ['HTTP_AUTH_TOKEN']= req.environ['HTTP_X_AUTH_TOKEN']
+ compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
+ cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
+
+ try:
+ #Up-to-date flavors and images
+ self.refresh_images(compClient,cyclClient)
+ self.refresh_flavors_norecursive(compClient,cyclClient)
+ self.refresh_network_instances(cyclClient)
+ self.refresh_compute_instances(compClient,cyclClient)
+ # token will be represented in self.extras
+ return self._call_occi(environ, response, security = None, token = environ['HTTP_AUTH_TOKEN'], snf = compClient, client = cyclClient)
+ except HTTPError:
+ print "Exception from unauthorized access!"
+ status = '401 Not Authorized'
+ headers = [('Content-Type', 'text/html'),('Www-Authenticate',auth_endpoint)]
+ response(status,headers)
+ return [str(response)]
+
+ else:
+
+ #raise HTTPError(404, "Unauthorized access")
+ status = '401 Not Authorized'
+ headers = [('Content-Type', 'text/html'),('Www-Authenticate',auth_endpoint)]
+ response(status,headers)
+ return [str(response)]
+
+ else:
compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
#Up-to-date flavors and images
- print "@refresh_images"
+
self.refresh_images(compClient,cyclClient)
- print "@refresh_flavors"
+
self.refresh_flavors_norecursive(compClient,cyclClient)
self.refresh_network_instances(cyclClient)
- print "@refresh_compute_instances"
self.refresh_compute_instances(compClient,cyclClient)
-
+
# token will be represented in self.extras
return self._call_occi(environ, response, security = None, token = environ['HTTP_AUTH_TOKEN'], snf = compClient, client = cyclClient)
+def application(env, start_response):
+
+ print "snf-occi will execute voms authentication"
+ t =snf_voms.VomsAuthN()
+ (user_dn, user_vo, user_fqans) = t.process_request(env)
+ print (user_dn, user_vo, user_fqans)
+
+ env['HTTP_AUTH_TOKEN'] = get_user_token(user_dn)
+
+ # Get user authentication details
+ astakosClient = astakos.AstakosClient(KAMAKI_CONFIG['astakos_url'], env['HTTP_AUTH_TOKEN'])
+ user_details = astakosClient.authenticate()
+
+ response = {'access': {'token':{'issued_at':'','expires': user_details['access']['token']['expires'] , 'id':env['HTTP_AUTH_TOKEN']},
+ 'serviceCatalog': [],
+ 'user':{'username': user_dn,'roles_links':user_details['access']['user']['roles_links'],'id': user_details['access']['user']['id'], 'roles':[], 'name':user_dn },
+ 'metadata': {'is_admin': 0, 'roles': user_details['access']['user']['roles']}}}
+
+
+ status = '200 OK'
+ headers = [('Content-Type', 'application/json')]
+ start_response(status,headers)
-def main():
+ body = json.dumps(response)
+ print body
+ return [body]
- APP = MyAPP(registry = snfRegistry())
- COMPUTE_BACKEND = ComputeBackend()
- NETWORK_BACKEND = NetworkBackend()
- NETWORKINTERFACE_BACKEND = NetworkInterfaceBackend()
- IPNETWORK_BACKEND = IpNetworkBackend()
- IPNETWORKINTERFACE_BACKEND = IpNetworkInterfaceBackend()
-
- APP.register_backend(COMPUTE, COMPUTE_BACKEND)
- APP.register_backend(START, COMPUTE_BACKEND)
- APP.register_backend(STOP, COMPUTE_BACKEND)
- APP.register_backend(RESTART, COMPUTE_BACKEND)
- APP.register_backend(SUSPEND, COMPUTE_BACKEND)
- APP.register_backend(RESOURCE_TEMPLATE, MixinBackend())
- APP.register_backend(OS_TEMPLATE, MixinBackend())
+
+def app_factory(global_config, **local_config):
+ """This function wraps our simple WSGI app so it
+ can be used with paste.deploy"""
+ return application
+
+def tenant_application(env, start_response):
+
+ print "snf-occi will return tenant information"
+ if env.has_key('SSL_CLIENT_S_DN_ENV'):
+ print env['SSL_CLIENT_S_DN_ENV'], env['SSL_CLIENT_CERT_ENV']
- # Network related backends
- APP.register_backend(NETWORK, NETWORK_BACKEND)
- APP.register_backend(IPNETWORK, IPNETWORK_BACKEND)
- APP.register_backend(NETWORKINTERFACE,NETWORKINTERFACE_BACKEND)
- APP.register_backend(IPNETWORKINTERFACE, IPNETWORKINTERFACE_BACKEND)
-
- VALIDATOR_APP = validator(APP)
-
- HTTPD = make_server('', SERVER_CONFIG['port'], VALIDATOR_APP)
- HTTPD.serve_forever()
+ req = Request(env)
+ if req.environ.has_key('HTTP_X_AUTH_TOKEN'):
+ env['HTTP_AUTH_TOKEN']= req.environ['HTTP_X_AUTH_TOKEN']
+ else:
+ raise HTTPError(404, "Unauthorized access")
+ # Get user authentication details
+ print "@ refresh_user authentication details"
+ astakosClient = astakos.AstakosClient(KAMAKI_CONFIG['astakos_url'], env['HTTP_AUTH_TOKEN'])
+ user_details = astakosClient.authenticate()
+
+ response = {'tenants_links': [], 'tenants':[{'description':'Instances of EGI Federated Clouds TF','enabled': True, 'id':user_details['access']['user']['id'],'name':'EGI_FCTF'}]}
+
+ status = '200 OK'
+ headers = [('Content-Type', 'application/json')]
+ start_response(status,headers)
+
+ body = json.dumps(response)
+ print body
+ return [body]
+
+
+def tenant_app_factory(global_config, **local_config):
+ """This function wraps our simple WSGI app so it
+ can be used with paste.deploy"""
+ return tenant_application
+
+
+def occify_terms(term_name):
+ '''
+ Occifies a term_name so that it is compliant with GFD 185.
+ '''
+ term = term_name.strip().replace(' ', '_').replace('.', '-').lower()
+ term=term.replace('(','_').replace(')','_').replace('@','_').replace('+','-_')
+ return term
+
+def get_user_token(user_dn):
+ config = kamaki_config.Config()
+ return config.get_cloud("default", "token")
--- /dev/null
+# Copyright 2012 Spanish National Research Council
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import M2Crypto
+import ast
+
+from logging import getLogger
+import voms_helper
+from kamaki.clients import ClientError
+
+LOG = getLogger(__name__)
+
+# Environment variable used to pass the request context
+CONTEXT_ENV = 'snf.context'
+
+
+SSL_CLIENT_S_DN_ENV = "SSL_CLIENT_S_DN"
+SSL_CLIENT_CERT_ENV = "SSL_CLIENT_CERT"
+SSL_CLIENT_CERT_CHAIN_ENV_PREFIX = "SSL_CLIENT_CERT_CHAIN_"
+
+"""Global variables that contain VOMS related paths
+"""
+VOMS_POLICY = "/etc/snf/voms.json"
+VOMSDIR_PATH = "/etc/grid-security/vomsdir/"
+CA_PATH = "/etc/grid-security/certificates/"
+VOMSAPI_LIB = "/usr/lib/libvomsapi.so.1"
+PARAMS_ENV = 'snf_voms.params'
+
+class VomsAuthN():
+ """Filter that checks for the SSL data in the reqest.
+
+ Sets 'ssl' in the context as a dictionary containing this data.
+ """
+
+ def __init__(self, *args, **kwargs):
+
+
+ # VOMS stuff
+ try:
+ self.voms_json = json.loads(
+ open(VOMS_POLICY).read())
+ except ValueError:
+ raise ClientError(
+ 'Bad Formatted VOMS json',
+ details='The VOMS json data was not corectly formatted in file %s' % VOMS_POLICY)
+ except:
+ raise ClientError(
+ 'No loading of VOMS json file',
+ details='The VOMS json file located in %s was not loaded' % VOMS_POLICY)
+
+ self._no_verify = False
+
+ #super(VomsAuthN, self).__init__(*args, **kwargs)
+
+ @staticmethod
+ def _get_cert_chain(ssl_info):
+ """Return certificate and chain from the ssl info in M2Crypto format"""
+
+ cert = M2Crypto.X509.load_cert_string(ssl_info.get("cert", ""))
+ chain = M2Crypto.X509.X509_Stack()
+ for c in ssl_info.get("chain", []):
+ aux = M2Crypto.X509.load_cert_string(c)
+ chain.push(aux)
+ return cert, chain
+
+ def _get_voms_info(self, ssl_info):
+ """Extract voms info from ssl_info and return dict with it."""
+
+ try:
+ cert, chain = self._get_cert_chain(ssl_info)
+ except M2Crypto.X509.X509Error:
+ raise ClientError(
+ 'SSL data not verified',
+ details=CONTEXT_ENV)
+
+ with voms_helper.VOMS(VOMSDIR_PATH,
+ CA_PATH, VOMSAPI_LIB) as v:
+ if self._no_verify:
+ v.set_no_verify()
+
+ voms_data = v.retrieve(cert, chain)
+
+ if not voms_data:
+ raise VomsError(v.error.value)
+
+ d = {}
+ for attr in ('user', 'userca', 'server', 'serverca',
+ 'voname', 'uri', 'version', 'serial',
+ ('not_before', 'date1'), ('not_after', 'date2')):
+ if isinstance(attr, basestring):
+ d[attr] = getattr(voms_data, attr)
+ else:
+ d[attr[0]] = getattr(voms_data, attr[1])
+
+ d["fqans"] = []
+ for fqan in iter(voms_data.fqan):
+ if fqan is None:
+ break
+ d["fqans"].append(fqan)
+
+ return d
+
+ @staticmethod
+ def _split_fqan(fqan):
+ """
+ gets a fqan and returns a tuple containing
+ (vo/groups, role, capability)
+ """
+ l = fqan.split("/")
+ capability = l.pop().split("=")[-1]
+ role = l.pop().split("=")[-1]
+ vogroup = "/".join(l)
+ return (vogroup, role, capability)
+
+ def _process_environ(self, environ):
+
+ LOG.warning("Getting the environment parameters...")
+ # the environment variable CONTENT_LENGTH may be empty or missing
+ try:
+ request_body_size = int(environ.get('CONTENT_LENGTH', 0))
+ except (ValueError):
+ request_body_size = 0
+ raise ClientError(
+ 'Not auth method provided',
+ details='The request body is empty, while it should contain the authentication method')
+
+ request_body = environ['wsgi.input'].read(request_body_size)
+
+ print request_body
+
+ request_body = request_body.replace("true","\"true\"")
+ request_body = request_body.replace('"','\'' )
+
+ params_parsed = ast.literal_eval(request_body)
+
+
+ params = {}
+ for k, v in params_parsed.iteritems():
+ if k in ('self', 'context'):
+ continue
+ if k.startswith('_'):
+ continue
+ params[k] = v
+
+
+ environ[PARAMS_ENV] = params
+ print environ[PARAMS_ENV]
+
+ def is_applicable(self, environ):
+ """Check if the request is applicable for this handler or not"""
+ print "Checking if the request is applicable for this handler or not..."
+ self._process_environ(environ)
+ params = environ.get(PARAMS_ENV, {})
+ auth = params.get("auth", {})
+ if "voms" in auth:
+ if "true" in auth["voms"]:
+ return True
+ else:
+ raise ClientError(
+ 'Error in json',
+ details='Error in JSON, voms must be set to true')
+
+ return False
+
+
+ def authenticate(self,ssl_data):
+
+ try:
+ voms_info = self._get_voms_info(ssl_data)
+ except VomsError as e:
+ raise e
+ user_dn = voms_info["user"]
+ user_vo = voms_info["voname"]
+ user_fqans = voms_info["fqans"]
+
+ return user_dn, user_vo, user_fqans
+
+
+ def process_request(self, environ):
+
+ print "Inside process_Request at last!!!!"
+ if not self.is_applicable(environ):
+ return self.application
+
+ ssl_dict = {
+ "dn": environ.get(SSL_CLIENT_S_DN_ENV, None),
+ "cert": environ.get(SSL_CLIENT_CERT_ENV, None),
+ "chain": [],
+ }
+ for k, v in environ.iteritems():
+ if k.startswith(SSL_CLIENT_CERT_CHAIN_ENV_PREFIX):
+ ssl_dict["chain"].append(v)
+
+ voms_info = self._get_voms_info(ssl_dict)
+
+ params = environ[PARAMS_ENV]
+
+ tenant_from_req = params["auth"].get("tenantName", None)
+
+ print voms_info, tenant_from_req
+ user_dn = voms_info["user"]
+ user_vo = voms_info["voname"]
+ user_fqans = voms_info["fqans"]
+ environ['REMOTE_USER'] = user_dn
+
+ return user_dn, user_vo, user_fqans
--- /dev/null
+# Copyright 2012 Spanish National Research Council
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import ctypes
+
+#import M2Crypto
+
+
+class _voms(ctypes.Structure):
+ _fields_ = [
+ ("siglen", ctypes.c_int32),
+ ("signature", ctypes.c_char_p),
+ ("user", ctypes.c_char_p),
+ ("userca", ctypes.c_char_p),
+ ("server", ctypes.c_char_p),
+ ("serverca", ctypes.c_char_p),
+ ("voname", ctypes.c_char_p),
+ ("uri", ctypes.c_char_p),
+ ("date1", ctypes.c_char_p),
+ ("date2", ctypes.c_char_p),
+ ("type", ctypes.c_int32),
+ ("std", ctypes.c_void_p),
+ ("custom", ctypes.c_char_p),
+ ("datalen", ctypes.c_int32),
+ ("version", ctypes.c_int32),
+ ("fqan", ctypes.POINTER(ctypes.c_char_p)),
+ ("serial", ctypes.c_char_p),
+ ("ac", ctypes.c_void_p),
+ ("holder", ctypes.c_void_p),
+ ]
+
+
+class _vomsdata(ctypes.Structure):
+ _fields_ = [
+ ("cdir", ctypes.c_char_p),
+ ("vdir", ctypes.c_char_p),
+ ("data", ctypes.POINTER(ctypes.POINTER(_voms))),
+ ("workvo", ctypes.c_char_p),
+ ("extra_data", ctypes.c_char_p),
+ ("volen", ctypes.c_int32),
+ ("extralen", ctypes.c_int32),
+ ("real", ctypes.c_void_p),
+ ]
+
+
+class VOMS(object):
+ """Context Manager for VOMS handling"""
+
+ def __init__(self, vomsdir_path, ca_path, vomsapi_lib):
+ self.VOMSApi = ctypes.CDLL(vomsapi_lib)
+ self.VOMSApi.VOMS_Init.restype = ctypes.POINTER(_vomsdata)
+
+ self.VOMSDIR = vomsdir_path
+ self.CADIR = ca_path
+
+ self.vd = None
+
+ def __enter__(self):
+ self.vd = self.VOMSApi.VOMS_Init(self.VOMSDIR, self.CADIR).contents
+ return self
+
+ def set_no_verify(self):
+ """Skip verification of AC.
+
+ This method skips the AC signature verification, this it should
+ only be used for debugging and tests.
+ """
+
+ error = ctypes.c_int32(0)
+ self.VOMSApi.VOMS_SetVerificationType(0x040,
+ ctypes.byref(self.vd),
+ ctypes.byref(error))
+
+ def retrieve(self, cert, chain):
+ """Retrieve VOMS credentials from a certificate and chain."""
+
+ self.error = ctypes.c_int32(0)
+
+ cert_ptr = ctypes.cast(long(cert._ptr()), ctypes.c_void_p)
+ chain_ptr = ctypes.cast(long(chain._ptr()), ctypes.c_void_p)
+
+ res = self.VOMSApi.VOMS_Retrieve(cert_ptr,
+ chain_ptr,
+ 0,
+ ctypes.byref(self.vd),
+ ctypes.byref(self.error))
+ if res == 0:
+ return None
+ else:
+ return self.vd.data.contents.contents
+
+ def __exit__(self, type, value, tb):
+ self.VOMSApi.VOMS_Destroy(ctypes.byref(self.vd))
\ No newline at end of file