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};
}