Wir von Nuxeo sind immer auf der Suche nach neuen Möglichkeiten, unsere Nuxeo Platform mit modernsten Technologien auszustatten. Diese Tatsache und meine Begeisterung für Produkte von Amazon inspirierten mich vor einigen Monaten dazu, eine Nuxeo Alexa Skill zu entwickeln, die es möglich macht, Dokumente der Nuxeo Platform mit Alexa zu suchen. Als das neue Echo Show vorgestellt wurde, sah ich darin die Gelegenheit, die Integration von Nuxeo und Alexa weiter zu verbessern - indem ich die Möglichkeit hinzufügte, Dokumente im Bildschirm von Echo Show anzuzeigen. Das bedeutet, dass Sie Alexa nun dazu auffordern können, digitale Assets zu durchsuchen und die Ergebnisse auf dem Bildschirm anzuzeigen, ohne dass Sie auch nur einen einzigen Finger rühren müssen.

Sehen wir uns diese Funktion doch einmal live und in Aktion an.

Zusammenfassung der alten Version

In meinem letzten Alexa-Blog habe ich Ihnen gezeigt, wie Sie eine Funktion entwickeln können, mit deren Hilfe Sie Ihr Alexa-Konto über einen Authentifizierungsbildschirm mit Ihrer Nuxeo-Instanz verknüpfen.

Unsere 3 Hauptbefehle

  • Search: Suche in den eigenen Dokumenten
  • GetLast: Anzeige Ihrer letzten Dokumente
  • Tasks: Anzeige der letzten Aufgaben

Die Architektur sah wie folgt aus:

Architektur

Werfen wir nun einen Blick auf die Schritte, die für eine Darstellung der Ergebnisse in Echo Show erforderlich sind.

Darstellung

Möchten Sie die Daten in Echo Show anzeigen, müssen Sie der Antwort einfach eine weitere Anweisung hinzufügen.

  {
    "type": "Display.RenderTemplate",
    "template": {...}
  }

In der aktuellen Liste stehen Ihnen sechs verschiedene Layouttypen zur Verfügung:

BodyTemplate1 BodyTemplate2 BodyTemplate3
BodyTemplate1 BodyTemplate2 BodyTemplate3
BodyTemplate6 ListTemplate1 ListTemplate2
BodyTemplate6 ListTemplate1 ListTemplate2

Aufgaben

Für die Aufgaben habe ich mich für "ListTemplate1" entschieden, da die Miniaturen hier nicht wichtig sind. Ich habe unseren Code aktualisiert und die entsprechenden Zeilen hinzugefügt. Hier ein Vergleich des Codes vor und nach der Aktualisierung:

Vorher

var tasks = '';
for (let id in doc.entries) {
    let res = "Task " + i++ + " " + ...;
    tasks += res;
    response.say(res);
}

Nachher

var tasks = '';
var listItems = [];
for (let id in doc.entries) {
    let res = "Task " + i++ + " " + ...;
    tasks += res;
    response.say(res);
    listItems.push(
      {
        ...
        "textContent": {
            "primartyText": {
              ...
            }
        }
      });
}
let template = {
  "type": "ListTemplate1",
  "token": "workflow",
  "title": "Your workflow tasks",
  "listItems": listItems
};
response.directive({"type": "Display.RenderTemplate", "template": template });

Für die Darstellung unserer Dokumente nach einem Befehl des Typs "Search" oder "GetLast" habe ich mich für "ListTemplate2" entschieden, da ich die Miniaturen des Dokuments anzeigen möchte.

Da die URL der Miniatur jedoch authentifiziert werden muss, würde der Abruf der Miniatur fehlschlagen. Wir müssen das System also überlisten und eine Proxy-Anfrage erstellen. Anstatt die Rest-API-URL der Miniatur zu verwenden:

{nuxeo_url}/api/v1/{uuid}/@rendition/thumbnail

Leiten wir sie über unseren Authentifizierungsserver um:

https://nuxeo-auth.loopingz.com/thumbnail.php?token={token}&uid={uid}

Proxy-Miniaturen

Die neue Architektur sieht wie folgt aus:

Neue Architektur

Die Erstellung der Proxy-Miniatur ist einfach - mit dem Authentifizierungs-Token wird der Nuxeo-PHP-API-Client aktiviert und eine GET-Aktion für die Wiedergabe der Miniatur eingeleitet. Sollte diese Aktion fehlschlagen, wird für das Dokument ein Standardbild angezeigt.

...
try {
   $client = new NuxeoClient($url);
   $client = $client->setAuthenticationMethod(new TokenAuthentication($token));

   $doc = $client->automation('Document.Fetch')->param('value', $uid)->execute(Document::className);
   $thurl = $url."/api/v1/id/".$uid."/@rendition/thumbnail";
   $res = $client->get($thurl);
   header("Content-Type: ".$res->getContentType());
   header("Content-Length: " . $res->getContentLength());
   print($res->getBody());
} catch (Exception $e) {
  defaultImage();
}

Sicherheit

Da der Token nun über einige URLs geleitet wird und lediglich mit base64 verschlüsselt war, habe ich mich entschlossen, den Token weiter zu sichern und AES-256 mit gemeinsamem Geheimnis zwischen Authentifizierungsserver und Lambda-Funktion einzusetzen.

Ich fügte also der Verknüpfung eine Verschlüsselung hinzu und der Proxy-Miniatur die entsprechende Entschlüsselung.

require 'secret.php';

function encrypt($data) {
  global $SECRET;
  $pass = openssl_digest($SECRET,"sha256", true);
  $iv = openssl_random_pseudo_bytes(16);
  $algo = "aes-256-ctr";
  $options = OPENSSL_RAW_DATA;
  $encData = openssl_encrypt($data, $algo, $pass, $options, $iv);
  return base64_encode($iv.$encData);
}

function decrypt($data) {
  global $SECRET;
  $pass = openssl_digest($SECRET,"sha256", true);
  $algo = "aes-256-ctr";
  $options = OPENSSL_RAW_DATA;
  $plain = base64_decode($data);
  return openssl_decrypt(substr($plain,16), $algo, $pass, $options, substr($plain,0,16));
}

Dann musste ich die Verschlüsselung von PHP zu NodeJS für die Alexa-Funktion anpassen.

const secret = require('./secret');

...

let tokenRaw = request.getSession().details.accessToken;
let raw = Buffer.from(tokenRaw, 'base64');
let iv = raw.slice(0,16);
let enc = raw.slice(16);
let cipher = crypto.createCipheriv(algo, secret, iv);
let dec = cipher.update(enc, 'ascii', 'ascii');
dec += cipher.final('ascii');

Es handelt sich hierbei eigentlich um eine Standardverschlüsselungsmethode - mit einem gemeinsamen 256-Bit-Schlüssel generieren wir einen zufälligen IV-Initialisierungsvektor. Mithilfe beider verschlüsseln wir unsere Daten und verbinden dann die IV-Vektoren mit den verschlüsselten Daten.

Sehen wir uns einmal an, wie das funktioniert

Hier eine Demo der fertigen Funktion:

Die Quellen finden Sie im GitHub Repository. Probieren Sie es aus und sagen Sie uns, was Sie davon halten!