PKI Client Python API Design - dogtagpki/pki GitHub Wiki
The goal of this document is to propose a design to provide a Python binding for PKI REST services.
This should provide the clients written in Python, access to the PKI resources (certs, cert requests, users etc.), exposed by the REST services of PKI. The REST services for PKI are defined here.
-
Provide a framework to access the PKI resources.
-
Hide all the miscellaneous features from the python client (such as connection, handling data etc.)
This is how the resources can be accessed currently by a python client.
connection = client.PKIConnection('http','localhost','8080','ca') connection.authenticate('caadmin', 'XXXX') url = '/rest/certs/0x6' r = self.connection.get(url, self.headers) e.TYPES['CertData'] = CertData() certData = e.CustomTypeDecoder(r.json())
Similar implementation in the Java client API using the RESTEasy packages.
public class CertClient extends Client { public CertResource certClient; public CertClient(PKIClient client, String subsystem) throws URISyntaxException { super(client, subsystem, "cert"); init(); } public void init() throws URISyntaxException { certClient = createProxy(CertResource.class); } public CertData getCert(CertId id) { return certClient.getCert(id); } }
where the connection details are present in PKIClient and the CertResource is the resource for certificate related queries.
The CertResource is the interface for the client to sumit a query and get data. The implementation of the query is handled by the service class, CertService, on the server side.
@Path("") public interface CertResource { @GET @Path("certs/{id}") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public CertData getCert(@PathParam("id") CertId id); }
The RESTEasy packages provides the annotations which handle the background tasks.
A similar framework is needed for the python clients, where the clients can access the resource without writing additional code to handle the connection, data transfer etc. details.
The modules providing the REST services can be placed in ./base/common/python/pki/ in the code tree as independent modules like (cert/user/group/<subsystem>) and can be imported in the client code from pki.<module_name> after installing the rpms. Each module provides methods to access the specific REST resources using the connection(pki.client.PKIConnection) object passed by the client. The PKIConnection object will also provide API for client cert authentication to perform privileged operations using the REST Interface.
Data objects contain the information required by the operation. The objects are serialized as a JSON object and attached as a payload over the HTTP request.
Following is the example of how the certificate resource can be accessed by the client.
In base/common/python/pki/cert/certresource.py,
class CertId: def __init__(self, id): if str(id).startswith('0x'): #hex number print 'hex number' self.id = id else: self.id = id class CertData: def __init__(self): pass def from_dict(self, attr_list): for key in attr_list: setattr(self, key, attr_list[key]) return self class CertDataInfo: def __init__(self): self.certId = None self.subjectDN = None self.status = None self.type=None self.version=None self.keyAlgorithmOID = None self.keyLength = None self.notValidBefore = None self.notValidAfter = None self.issuedOn = None self.issuedBy = None def from_dict(self, attr_list): for key in attr_list: setattr(self, key, attr_list[key]) return self class CertDataInfos: def __init__(self): self.certInfoList = [] self.links = [] def decode_from_json(self, jsonValue): certInfos = jsonValue['CertDataInfo'] print certInfos if not isinstance(certInfos, types.ListType): certInfo = CertDataInfo() self.certInfoList.append(certInfo.from_dict(certInfos)) else: for certInfo in certInfos: certDataInfo = CertDataInfo() certDataInfo.from_dict(certInfo) self.certInfoList.append(certDataInfo) class CertSearchRequest: def __init__(self): self.serialNumberRangeInUse = False self.serialTo = None self.serialFrom = None self.subjectInUse = False self.eMail = None self.commonName = None self.userID = None self.orgUnit = None self.org = None self.locality = None self.state = None self.country = None self.matchExactly = None self.status = None self.revokedBy = None self.revokedOnFrom = None self.revokedOnTo = None self.revocationReason = None self.issuedBy = None self.issuedOnFrom = None self.issuedOnTo = None ... class CertResource: #connection - PKIConnection object with the server details def __init__(self, connection): self.connection = connection self.headers = {'Content-type': 'application/json', 'Accept': 'application/json'} self.certURL = '/rest/certs' self.agentCertURL = '/rest/agent/certs' def getCert(self, certId): url = self.certURL + '/' + str(certId.id) r = self.connection.get(url, self.headers) e.TYPES['CertData'] = CertData() certData = e.CustomTypeDecoder(r.json()) return certData def searchCerts(self, cert_search_request): url = self.certURL + '/search' e.TYPES['CertSearchRequest'] = CertSearchRequest searchRequest = json.dumps(cert_search_request, cls=e.CustomTypeEncoder) r = self.connection.post(url, searchRequest, self.headers) print r.json()['CertDataInfos'] cdis = CertDataInfos() cdis.decode_from_json(r.json()['CertDataInfos']) return cdis
Note: CustomTypeEncoder provides encoding of custom objects into JSON. The CustomTypeDecoder() provides the decode mechanism. Both are present in base/common/python/pki/encoder.py.
How the client can access the resources:
import pki.cert.certresource as cert_client import pki.client as client def main(): connection = client.PKIConnection('http','localhost','8080','ca') connection.authenticate('caadmin', 'XXXX') cR = cert_client.CertResource(connection) cert = cR.getCert(CertId('0x6')) print str(cert) # A Pretty Print method has to be provided if __name__ == "__main__": main()