[Bps-public-commit] rt-extension-assets-applegsx branch, uat, created. 1.0-11-gf1abf55
Michel Rodriguez
michel at bestpractical.com
Wed Aug 28 16:45:07 EDT 2019
The branch, uat has been created
at f1abf551e6349562bfe923f7b3fdc112a0cc4da4 (commit)
- Log -----------------------------------------------------------------
commit f1abf551e6349562bfe923f7b3fdc112a0cc4da4
Author: michel <michel at bestpractical.com>
Date: Wed Aug 21 19:35:17 2019 +0200
The module now uses the new JSON based Apple GSX API
diff --git a/README b/README
index 706ea38..9cada11 100644
--- a/README
+++ b/README
@@ -38,6 +38,21 @@ CONFIGURATION
account number, you must then get certificate and key files from Apple
and your server IP addresses must be whitelisted by Apple.
+ The configuration for the service uses the following variables:
+
+ # test server
+ Set( $AppleGSXApiBase, 'https://partner-connect-uat.apple.com/gsx/api');
+ Set( $AppleGSXGetToken, 'https://gsx2-uat.apple.com/gsx/api/login');
+
+ or
+
+ # production server
+ Set( $AppleGSXApiBase, 'https://partner-connect.apple.com/gsx/api');
+ Set( $AppleGSXGetToken, 'https://gsx2.apple.com/gsx/api/login');
+
+ plus the user ID that you use to get the initial activation token
+ Set( $AppleGSXUserId, '<Apple user ID, an email address');
+
Once you have done this, you can configure the authentication
information used to connect to GSX via the web UI, at Tools ->
Configuration -> Assets -> Apple GSX. This menu option is only available
diff --git a/html/Admin/Assets/GSX/index.html b/html/Admin/Assets/GSX/index.html
index 25d5ac7..2b8ce6b 100644
--- a/html/Admin/Assets/GSX/index.html
+++ b/html/Admin/Assets/GSX/index.html
@@ -32,6 +32,11 @@ Unable to connect to the Apple GSX services using the provided account informati
<tr><td class="label"><label for="KeyFilePath"><&|/l&>Key File Path</&></label></td>
<td><input name="KeyFilePath" id="KeyFilePath" value="<% $KeyFilePath %>" size="60" /></td>
</tr>
+<tr><td class="label"><label for="ActivationToken"><&|/l&>Activation Token</&></label></td>
+ <td><input name="ActivationToken" id="ActivationToken" value="<% $ActivationToken %>" size="60" /><br />
+ In case of problem get a new activation token from <a href="<% $get_activation_token_url %>"><% $get_activation_token_url %></a>.</td>
+</tr>
+
</table>
<& /Elements/Submit, Name => "Update", Label => loc('Update') &>
@@ -42,6 +47,10 @@ $m->clear_and_abort(403) unless $session{'CurrentUser'}->HasRight(
Right => 'SuperUser',
);
+my $show_token = RT->Config->Get('AppleGSXShowAuthenticationToken');
+
+my $get_activation_token_url = RT->Config->Get('AppleGSXGetToken');
+
my $config = RT->System->FirstAttribute('AppleGSXOptions');
$config = $config ? $config->Content : {};
if ($ARGS{Update}) {
@@ -51,6 +60,7 @@ if ($ARGS{Update}) {
$config->{LanguageCode} = $LanguageCode;
$config->{CertFilePath} = $CertFilePath;
$config->{KeyFilePath} = $KeyFilePath;
+ $config->{ActivationToken} = $ActivationToken;
RT->System->SetAttribute( Name => 'AppleGSXOptions', Content => $config );
}
@@ -61,6 +71,7 @@ my $ok = $config->{UserId}
&& $config->{LanguageCode}
&& $config->{CertFilePath}
&& $config->{KeyFilePath}
+ && $config->{ActivationToken}
&& $gsx->Authenticate;
$UserId = $config->{UserId};
@@ -69,6 +80,8 @@ $UserTimeZone = $config->{UserTimeZone} if $config->{UserTimeZone};
$LanguageCode = $config->{LanguageCode} if $config->{LanguageCode};
$CertFilePath = $config->{CertFilePath};
$KeyFilePath = $config->{KeyFilePath};
+$ActivationToken = $config->{ActivationToken};
+
</%init>
<%args>
$UserId => ""
@@ -77,4 +90,5 @@ $UserTimeZone => "PST"
$LanguageCode => "en"
$CertFilePath => ""
$KeyFilePath => ""
+$ActivationToken => ""
</%args>
diff --git a/lib/RT/Extension/Assets/AppleGSX.pm b/lib/RT/Extension/Assets/AppleGSX.pm
index 4007c04..4e1a20a 100644
--- a/lib/RT/Extension/Assets/AppleGSX.pm
+++ b/lib/RT/Extension/Assets/AppleGSX.pm
@@ -3,7 +3,7 @@ use warnings;
package RT::Extension::Assets::AppleGSX;
use RT::Extension::Assets::AppleGSX::Client;
-our $VERSION = '1.2';
+our $VERSION = '2.0';
my $CLIENT;
my $CLIENT_CACHE;
@@ -27,7 +27,7 @@ sub SerialCF {
sub Fields {
return RT->Config->Get('AppleGSXMap') || {
- 'Warranty Status' => 'warrantyStatus',
+ 'Warranty Status' => 'warrantyStatusCode',
'Warranty Start Date' => 'coverageStartDate',
'Warranty End Date' => 'coverageEndDate',
};
@@ -82,22 +82,18 @@ sub UpdateGSX {
return (0, "Apple GSX authentication failed; cannot import data")
unless $CLIENT->Authenticate;
- if ( my $serial = $self->FirstCustomFieldValue($serial_name) ) {
- my $info = $CLIENT->WarrantyStatus($serial);
- return (0, "GSX contains no information (check $serial_name?)")
- unless $info;
-
- # GSX returns everything in mm/dd/yy format. Sadly, local'ing
- # $RT::DateDayBeforeMonth is insufficient (?!). We set it back,
- # below; ensure that this function does not return between these
- # two statements!
- my $date_order = RT->Config->Get("DateDayBeforeMonth");
- RT->Config->Set("DateDayBeforeMonth" => 0);
+ if ( my $serial = $self->FirstCustomFieldValue( $serial_name ) ) {
+ my( $ret, $msg, $device ) = $CLIENT->GetDataForSerial( $serial );
+ if( ! $ret ) {
+ return (0, $msg)
+ }
my @results;
for my $field ( keys %$FIELDS_MAP ) {
my $old = $self->FirstCustomFieldValue($field);
- my $new = $info->{warrantyDetailInfo}{ $FIELDS_MAP->{$field} };
+ # data is either at device level or in $device->{warrantyInfo}
+ # the old mapping doesn't know about those 2 levels so we look in both places
+ my $new = $device->{ $FIELDS_MAP->{$field} } || $device->{warrantyInfo}{ $FIELDS_MAP->{$field} };
if ( defined $new ) {
# Canonicalize date and datetime CFs
if ($self->LoadCustomFieldByIdentifier($field)->Type =~ /^date(time)?/i) {
@@ -123,8 +119,6 @@ sub UpdateGSX {
}
}
- RT->Config->Set("DateDayBeforeMonth" => $date_order);
-
return (1, @results);
}
else {
diff --git a/lib/RT/Extension/Assets/AppleGSX/Client.pm b/lib/RT/Extension/Assets/AppleGSX/Client.pm
index c0e536a..85a45b2 100644
--- a/lib/RT/Extension/Assets/AppleGSX/Client.pm
+++ b/lib/RT/Extension/Assets/AppleGSX/Client.pm
@@ -6,52 +6,48 @@ package RT::Extension::Assets::AppleGSX::Client;
use Net::SSL;
use LWP::UserAgent;
-use XML::Simple;
-my $xs = XML::Simple->new;
+use JSON;
use base 'Class::Accessor::Fast';
__PACKAGE__->mk_accessors(
- qw/UserAgent UserSessionId UserSessionTimeout UserId UserTimeZone
- ServiceAccountNo LanguageCode CertFilePath KeyFilePath/
+ qw/UserAgent ActivationToken AuthenticationToken UserId UserTimeZone
+ ServiceAccountNo LanguageCode CertFilePath KeyFilePath AppleGSXApiBase/
);
sub new {
my $class = shift;
my $args = ref $_[0] eq 'HASH' ? shift @_ : {@_};
my $self = $class->SUPER::new($args);
+
$ENV{HTTPS_CERT_FILE} = $self->CertFilePath;
$ENV{HTTPS_KEY_FILE} = $self->KeyFilePath;
+ my $store_code = sprintf( "%010d", $self->ServiceAccountNo);
+
$self->UserAgent( LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 }) ) unless $self->UserAgent;
+ my $default_headers = HTTP::Headers->new(
+ 'X-Apple-SoldTo' => $store_code,
+ 'X-Apple-ShipTo' => $store_code,
+ );
+ $self->UserAgent->default_headers( $default_headers );
+
+ # by default use the testing (-uat) URLs for both the API and getting the initial token
+ $self->{AppleGSXApiBase} ||= 'https://partner-connect-uat.apple.com/gsx/api';
+ $self->{AppleGSXGetToken} ||= 'https://gsx2-uat.apple.com/gsx/api/login';
+
return $self;
}
+# may need a name change, this does not authenticate, but just checks that the API is accessible
sub Authenticate {
my $self = shift;
- my $xml = $self->PrepareXML(
- 'Authenticate',
- {
- userId => $self->UserId,
- serviceAccountNo => $self->ServiceAccountNo,
- languageCode => $self->LanguageCode,
- userTimeZone => $self->UserTimeZone,
- }
- );
-
- my $res = $self->SendRequest($xml);
+ my %headers = ( Accept => 'text/plain' );
+ my $res = $self->UserAgent->get( $self->AppleGSXApiBase . "/authenticate/check", %headers );
if ( $res->is_success ) {
- my $ret =
- $self->ParseResponseXML( 'Authenticate', $res->decoded_content );
- $self->UserSessionId( $ret->{'userSessionId'} );
-
- # official timeout is 30 minutes, minus 5 is to avoid potential
- # out of sync time issue
- $self->UserSessionTimeout( time() + 25 * 60 );
- return $self->UserSessionId;
+ return 1;
}
else {
- warn "Failed to authenticate to Apple GSX: " . $res->status_line;
- warn "Full response: " . $res->content;
+ RT->Logger->error( "Failed to authenticate to Apple GSX: " . $res->status_line );
return;
}
}
@@ -60,91 +56,107 @@ sub WarrantyStatus {
my $self = shift;
my $serial = shift or return;
- $self->Authenticate
- unless $self->UserSessionId && time() < $self->UserSessionTimeout;
+ my( $ret, $msg, $device )= $self->GetDataForSerial( $serial );
+ if( ! $ret ) {
+ return( 0, $msg, undef);
+ }
+ if( ! $device->{warrantyInfo} ) {
+ RT->Logger->warning( "no warantyInfo returned (for sn $serial)" );
+ return( 0, "no warantyInfo returned" );
+ }
+ return ( 1, '', $device->{warrantyInfo});
+}
+
+sub GetDataForSerial {
+ my $self = shift;
+ my $serial = shift or return;
+
+ my $token = $self->AuthenticationToken;
- my $xml = $self->PrepareXML(
- 'WarrantyStatus',
- {
- 'userSession' => { userSessionId => $self->UserSessionId, },
- 'unitDetail' => { serialNumber => $serial,
- shipTo => $self->ServiceAccountNo }
- }
+ my %headers = (
+ 'X-Apple-Auth-Token' => $token,
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
);
- for my $try (1..5) {
- my $res = $self->SendRequest($xml);
- unless ($res->is_success) {
- my $data = eval {$xs->parse_string( $res->decoded_content, NoAttr => 1, SuppressEmpty => undef ) };
- my $fault = $data ? $data->{"S:Body"}{"S:Fault"}{"faultstring"} : $res->status_line;
- if ($fault =~ /^The serial number entered has been marked as obsolete/) {
- # no-op
- } elsif ($fault =~ /^The serial you entered is not valid/) {
- # no-op
- } else {
- warn "Failed to get Apple GSX warranty status of serial $serial: $fault";
- }
- return;
+ my $args = { "device" => { "id" => $serial } };
+ my $json = encode_json( $args );
+ my $response;
+
+ # only try if we have a token, otherwise we need to get one first
+ if( $token) {
+ $response = $self->UserAgent->post( $self->AppleGSXApiBase . "/repair/product/details", Content => $json, %headers );
+ }
+
+ if( ! $token || $response->code == 401 ) {
+ my( $ret, $msg, $new_token );
+ if( $token ) {
+ ( $ret, $msg, $new_token )= $self->get_new_authentication_token( $token );
+ }
+ if( ! $token || ! $ret) {
+ ( $ret, $msg, $new_token)= $self->get_new_authentication_token( $self->ActivationToken );
}
- my $ret = $self->ParseResponseXML( 'WarrantyStatus', $res->decoded_content );
- return $ret if $ret->{warrantyDetailInfo} and $ret->{warrantyDetailInfo}{serialNumber};
+ if( $ret) {
+ RT->Logger->debug( "got new authentication token");
+ $headers{'X-Apple-Auth-Token'} = $new_token;
+ $response = $self->UserAgent->post( $self->AppleGSXApiBase . "/repair/product/details", Content => $json, %headers);
+ }
+ else {
+ return ( 0, "error connecting to the GSX API: $msg", undef);
+ }
}
- warn "Repeatedly failed to get complete response from Apple GSX for serial $serial";
- return;
-}
-sub PrepareXML {
- my $self = shift;
- my $method = shift;
- my $args = shift || {};
-
- my $xml = $xs->XMLout(
- {
- 'soapenv:Body' =>
- { "glob:$method" => { "${method}Request" => $args, }, },
- },
- NoAttr => 1,
- KeyAttr => [],
- RootName => '',
- );
- return <<"EOF",
-<?xml version="1.0" encoding="UTF-8"?>
-<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
-xmlns:glob="http://gsxws.apple.com/elements/global">
-<soapenv:Header/>
-$xml
-</soapenv:Envelope>
-EOF
+ if( $response->is_success ) {
+ my $product_details = decode_json( $response->decoded_content );
+ my $device = $product_details->{device};
-}
+ # we set a couple of fields that were named differently in the old API, so old code still workd
+ # old warrantyStatus is new warrantyStatusDescription
+ $device->{warrantyInfo}->{warrantyStatus} = $device->{warrantyInfo}->{warrantyStatusDescription};
+ # old estimatedPurchaseDate is new purchaseDate (in warrantyInfo)
+ $device->{estimatedPurchaseDate} = $device->{warrantyInfo}->{purchaseDate};
-sub ParseResponseXML {
- my $self = shift;
- my $method = shift;
- my $xml = shift;
- my $ret = $xs->XMLin( $xml, NoAttr => 1, SuppressEmpty => undef, NSExpand => 1 );
- return $ret->{'{http://schemas.xmlsoap.org/soap/envelope/}Body'}
- ->{"{http://gsxws.apple.com/elements/global}${method}Response"}
- ->{"${method}Response"};
+ return( 1, '', $device);
+ }
+ else {
+ RT->Logger->warning( "Failed to get response from Apple GSX for serial $serial" );
+ return( 0, "Failed to get response from Apple GSX for serial $serial" );
+ }
}
-sub SendRequest {
+sub get_new_authentication_token {
my $self = shift;
- my $xml = shift;
+ my $old_token= shift;
+
+ my $data = { userAppleId => $self->UserId, authToken => $old_token };
+ my $json = encode_json( $data);
+ my %headers = (
+ 'Content-Type' => 'application/json',
+ Accept => 'application/json',
+ );
+ my $response = $self->UserAgent->post( $self->AppleGSXApiBase . "/authenticate/token", Content => $json, %headers );
+ if( $response->code == 200 ) {
+ my $json_string = $response->decoded_content;
+ my $response_json = decode_json( $json_string);
- my $domain = 'https://gsxapi.apple.com';
+ my $new_authentication_token = $response_json->{authToken};
- # Apple standard appears to be to use 'Test' for testing environment
- # certs.
- $domain = 'https://gsxapiut.apple.com' if $self->CertFilePath =~ /Test/;
+ $self->AuthenticationToken( $new_authentication_token);
- my $res = $self->UserAgent->post(
- "$domain/gsx-ws/services/am/asp",
- 'Content-Type' => 'text/xml; charset=utf-8',
- Content => $xml,
- );
- return $res;
+ # save the token in the AppleGSXOptions attribute
+ my $config= RT->System->FirstAttribute('AppleGSXOptions');
+ my $content = $config->Content;
+ $content->{AuthenticationToken} = $new_authentication_token;
+ # $config->SetContent( $content);
+ RT->System->SetAttribute( Name => 'AppleGSXOptions', Content => $content );
+
+ return ( 1, '', $new_authentication_token);
+ }
+ else {
+ RT->Logger->error( "Failed to get authentication token" );
+ return( 0, "cannot get authentication token: " . $response->code, undef);
+ }
}
1;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list