package DigiCert::Automation; =head1 DESCRIPTION Blah blah about Digicert. You need an API key to make calls. API keys blah blah blah Set L to the secret value. The API is documented at https://dev.digicert.com/en/certcentral-apis.html =cut use v5.36; use Moo; use Carp; use JSON::MaybeXS; use LWP::UserAgent; use Log::Any '$log', default_adapter => 'Stderr'; use URI; has base_uri => is => 'rw', default => 'https://caas.digicert.com/automationws/v1'; has api_key => is => 'rw', required => 1; has http_timeout => is => 'rw', default => 60, trigger => sub ($self, @) { $self->_user_agent->timeout($self->http_timeout) if $self->_has_user_agent; }; has _user_agent => ( is => 'lazy', predicate => 1 ); sub _build__user_agent($self, @) { my $ua= LWP::UserAgent->new(cookie_jar => {}); $ua->timeout($self->http_timeout) if defined $self->http_timeout; $ua->default_header(Accept => 'application/json'); $ua->default_header('X-DC-DEVKEY' => $self->api_key); $ua->add_handler('request_send', sub ($req, $ua, $h, @) { if ($log->is_trace) { my $req_text= $req->as_string; $req_text =~ s/^/ /mg; $log->trace("Digicert HTTP Request:\n$req_text"); } return; }); $ua->add_handler('response_done', sub ($res, $ua, $h, @) { if ($log->is_trace) { my $res_text= $res->as_string; $res_text =~ s/^/ /mg; $log->trace("Digicert HTTP Response:\n$res_text"); } return; }); push @{ $ua->requests_redirectable }, 'POST'; return $ua; } #### # Shorthand for attaching the auth token and running an API request. sub _user_agent_request($self, $req) { $log->debug("Digicert API: ".$req->method.' '.$req->uri) if $log->is_debug; my $startT= time; my $res= $self->_user_agent->request($req); my $endT= time; $log->infof("%s response: %s (took %.3f sec)", (!$log->is_debug? "Digicert API: ".$req->method.' '.$req->uri : ''), $res->status_line, $endT - $startT) if $log->is_debug or $endT-$startT > 3 or !$res->is_success; return $res; } # Shorthand that takes an HTTP response and either unpacks the data or dies. sub _return_data_or_die_informatively($self, $res, $req) { my $data; $data= JSON->new->allow_nonref->decode($res->decoded_content) if $res->content_type =~ m,^application/.*?json\z,; if ($res->is_success && defined $data && defined $data->{data}) { return $data->{data}; } else { croak "DigiCert ".$res->status_line." -- ".$res->decoded_content; } } #### # Shorthand private method for GET request with URL parameters sub _api_get($self, $uri_tail, $query_params) { my $uri= URI->new($self->base_uri . '/' . $uri_tail); $uri->query_form(%$query_params) if $query_params; my $req= HTTP::Request->new(GET => $uri, []); my $res= $self->_user_agent_request($req); return $self->_return_data_or_die_informatively($res, $req); } # Shorthand private method for POST request with JSON packet of parameters sub _api_post($self, $uri_tail, $query_params, $body_data) { my $uri= URI->new($self->base_uri . '/' . $uri_tail); $uri->query_form(%$query_params) if $query_params; my $req= HTTP::Request->new(POST => $uri, [Content_Type => 'application/json'], encode_json($body_data) ); my $res= $self->_user_agent_request($req); return $self->_return_data_or_die_informatively($res, $req); } #### perl -Ilib -MDDP -MDigicert::CertCentral -E '\ my $d= Digicert::CertCentral->new(api_key => $ENV{DIGICERT_API_KEY}); \ my $data= $d->_api_post("automation/viewAutomationDetails", { accountId => ... }); \ p $data;' #### =head2 api_viewAutomationDetails Input structure: { "accountId": 5153184, "automationId": 298577, "divisionId": 677793 } Output structure: "autoStatusResponse": { "automationStatus": "INSTALL_VALIDATION_FAILED", "requestedOn": "1599716536705", "installSettings": { "installationType": "AUTO_INSTALL_AFTER_APPROVAL", "isAlwaysOn": false }, "userDetailsResponse": { "firstName": "CertCentral", "lastName": "Admin", "container": { "id": 677793, "name": "Cert Testing Inc." } } }, "autoOrderProgressResponse": { "csrGenResponse": { "csrProgress": "CSR_GENERATION_SUCCEEDED" }, "certInstallResponse": { "certInstallProgress": "INSTALL_VALIDATION_FAILED", "causeOfFailure": "Post install validation is unsuccessful, could be due to installation failure / scan failure", "solution": "Verify certificate manually on website and cancel this automation request if installed, otherwise retry. Contact customer support for additional help." }, "isRetryApplicable": true }, "certDetailsResponse": { "commonName": "cert-testing.com", "signatureHash": "", "orgDetails": { "name": "Cert Testing Inc.", "address": "2801 N Thanksgiving Way", "address2": "Suite 500", "city": "Lehi", "state": "Utah", "telephone": "801-701-9600" }, "sans": "", "automationProfileName": "22jul2020 01", "automationProfileId": 741.0, "automationProfileStatus": "ACTIVE", "productType": "SSL_SECURESITE_FLEX", "validityPeriod": "1Y" }, "isRequestApproval": false https://dev.digicert.com/en/certcentral-apis/automation-api/view-automation-details.html =cut sub api_viewAutomationDetails($self, %params) { $params{accountId} //= $self->account_id; $params{divisionId} //= $self->division_id; length $params{automationId} or croak "automationId required"; $self->_api_post("automation/viewAutomationDetails", \%params); } #### package Digicert::CertCentral; =head2 automation $atn= $api->automation($id); Return a new or cached Automation object for the given ID. =cut sub automation($self, $id) { return $self->{_automation_cache}{$id}->refresh(10) if $self->{_automation_cache}{$id}; my $atn= Digicert::CertCentral::Automation->new(api => $self, id => $id); Scalar::Util::weaken($self->{_automation_cache}{$id}= $atn); $atn; } package Digicert::CertCentral::Automation; use v5.36; use Moo; has api => ( is => 'ro', required => 1 ); has id => ( is => 'ro', required => 1 ); has data => ( is => 'rw' ); has data_ts => ( is => 'rw' ); sub BUILD ($self, @) { # die if automation doesn't exist, before finishing constructor $self->refresh; } sub refresh($self, $max_age=0) { if (!$self->data_ts || !$max_age || time - $self->data_ts > $max_age) { $self->data($self->api->api_viewAutomationDetails(automationId => $self->id)); $self->data_ts(time); } $self; } sub status($self) { $self->data->{autoStatusResponse}{automationStatus}; }