Fitbit API Subscription

Fitbit Subscription Notifications mit Silverstripe CMS empfangen

Fitbit API Subscription

Fitbit bietet auf seiner API die Möglichkeit über Datenänderungen von Benutzer informiert zu werden. Dies ermöglicht der Applikation nur dann die Daten von der Fitbit API abzufragen wenn diese aktueller sind als der lokale Stand. Vor allem für Applikation welche viele verschiedene Ressourcen Calls an die API machen, werden durch die maximale 150 Request pro Stunde welche die Fitbit API zulässt eingeschränkt.

Die nachfolgende Beschreibung geht davon aus, das eine Subscription bei Fitbit eingerichtet werden konnte und quasi alles soweit vorbereitet ist das wir uns auf das Empfangen von Notifications mit dem Silverstripe CMS fokusieren können.

Am einfachsten bauen wir dazu eine eigene Schnittstelle, welche die Requests von Fitbit zukünftig entgegen nehmen wird. In Silverstripe bietet sich dazu einen eigenen Controller:

class FitBitController extends Controller {
  private static $allowed_actions = array (
    'Notification'
  );

  public function Notification(SS_HTTPRequest $request) {
 
    ...
  }
}

Das Routing kann in Silverstripe über YAML konfiguriert werden:

Director:
    rules:
        'api/fitbit//$Action': 'FitBitController'

Der Controller soll GET- und POST-Request Methoden entgegennehmen können. Der GET-Request wird von Fitbit beim anlegen oder ändern eines Subscriber gemacht, um einen Notification-Endpunkt zu verifizieren. Es werden zwei Request mit je einem korrekten Verify-Parameter und einem inkorrekten. Der Controller muss je mit einer 204-Status und einem 404-Status beantwortet werden:

// Receive GET Request from fitbit
if($request->httpMethod() == 'GET') {

    // Verify notification endpoint and compare verificationcode
    if ($this->CheckVerififcationCode($request)) {

        // OK
        $this->response->setStatusCode(204);
    }
    else {

        // Not OK
        $this->response->setStatusCode(404);
    }
}

 Die Verification wird über einen String Compare ausgeführt:

private function CheckVerififcationCode($request) {

    $verify = $request->getVar('verify');

    if (!empty($verify)) {

        $verification = $this->config->get('Subscriber', 'verififcation_code');

        if ($verify == $verification) {

            return true;
        }
    }

    return false;
}

Der POST-Request wird dann auf dem Teil des Controllers entgegen genommen auf dem die gesendeten Daten verarbeitet werden.

if ($request->httpMethod() == 'POST') {

    // Verify FitBit request Signature
    if ($this->VerifySignature($request)) {

        $collection = json_decode($request->getBody(), true);

        foreach ($collection as $entry) {

            // Mark record as dirty
            $this->ressourcenRepository->MarkAsDirty($entry['subscriptionId']);
        }

        $this->response->setStatusCode(204);
    }
}

Die Verification der Signatur welche im Request mitgesendet wird ist ein wichtiges Security Feature von Fitbit womit die eigene Schnittstelle gegen aussen geschütz werden kann. Es empfiehlt sich sehr diese Möglichkeit zu nutzen. Fitbit erstellt von jedem Request eine Signatur, welche im X-Fitbit-Signature Header mitgesendet werden. Die Signature basiert auf einem Base64 encodeten HMAC welcher einen SHA1 Algorithmus verwendet um den Request Body zu verhashen. Als Key wird der Client Secret mit einem & am Ende des Strings verwendet. Auf dem Notifications Endpunkt wird genau dasselbe gemacht und wenn das Ergebnis mit der Signatur im Header übereinstimmt kann die Information als sicher (Integrität) angesehen werden.

private function VerifySignature($request) {
		
    $expectedSignature = base64_encode(hash_hmac("sha1", $request->getBody(), $this->config->get('Client_Data', 'client_secret')."&", true));
    $signature = $request->getHeader("X-Fitbit-Signature");

    if ($signature == $expectedSignature) {

        return true;
    }

    return false;
}

  Alle anderen Requests werden mit einem Status Code 405 beantwortet und mit dem Hinweis auf die erlaubten Methoden ergänzt:

$this->response->addHeader('Allow', 'GET, POST');
$this->response->setStatusCode(405);