Статьи

Взлом Fitbit — эмуляция пейджера для мастеров Twitter!

Я пытался проснуться рано утром. Проблема в том, что будильник разбудил всех, а не только меня. Чтобы обойти эту проблему, я недавно купил самый дешевый Fitbit, который я смог найти, узнав, что у них аккуратная тихая тревога.

Правда в том, что если бы у меня были деньги, я бы предпочел купить часы Apple. Когда я получил Fitbit, мой мозг программиста сразу же перешел к вопросу; «Как я могу взломать эту вещь?»

Паяльник векторное изображение

В итоге я немного узнал о Fitbit, OAuth и Twitter API. Я также узнал, что иногда лучше просто купить часы Apple …

Код для этого урока можно найти на Github .

Начиная

Я решил попробовать использовать тихую сигнализацию в качестве системы уведомлений. Я бы проверил наличие новых сообщений в Твиттере и установил будильник как можно быстрее. Таким образом, когда я почувствовал тихую тревогу, которую я не ожидал, я мог проверить Twitter …

Оказывается, и Fitbit, и Twitter имеют JSON API, и оба проходят аутентификацию с помощью OAuth. Я начал с создания нового приложения Lumen , которое будет служить планировщиком задач и коммуникатором для этого проекта:

composer install laravel/lumen . 

Lumen — это просто упрощенная версия фреймворка Laravel . Есть несколько вещей, которые по умолчанию отключены, а другие настроены на «легкие» альтернативные реализации. Все, что вы строите в Lumen, может быть перенесено в Laravel, поэтому я подумал, что попробую это и посмотрю, достаточно ли он быстр и функционален для связи.

Связь должна происходить в два этапа:

  1. Создать ссылки / кнопки для подключения приложения к Twitter и Fitbit
  2. Запланируйте задачи командной строки, чтобы проверить наличие новых прямых сообщений и установить сигналы тревоги.

Я начал с добавления маршрутов для первого шага:

 $app->get("/", function() { return view("dashboard"); }); $app->get("/auth/fitbit", function() { // begin auth with fitbit }); $app->get("/auth/fitbit/callback", function() { // store oauth credentials }); $app->get("/auth/twitter", function() { // begin auth with twitter }); $app->get("/auth/twitter/callback", function() { // store oauth credentials }); 

Это происходит в app/Http/routes.php .

Я также должен был создать представление панели инструментов для них:

 <a href="{{ url("auth/fitbit") }}">connect with fitbit</a> <a href="{{ url("auth/twitter") }}">connect with twitter</a> 

Это происходит в resources/views/dashboard.blade.php .

Команда artisan serve была удалена из Lumen, но если вы действительно хотите вернуть ее (как я), вы можете установить ее с помощью:

 composer require mlntn/lumen-artisan-serve 

Вам также необходимо добавить \Mlntn\Console\Commands\Serve::class в список зарегистрированных консольных команд, в app/Console/Kernel.php .

Регистрация приложений

И Fitbit, и Twitter требовали, чтобы я зарегистрировал новые приложения, прежде чем они предоставят ключи OAuth, необходимые для аутентификации.

Fitbit новый экран приложения

Я разрабатывал все это локально, используя сервер Artisan, чтобы добраться до таких маршрутов, как http://localhost:8080/auth/twitter . Страница регистрации приложения Twitter не разрешает URL-адреса обратного вызова с локальным хостом, IP-адресом или номерами портов.

Я собирался настроить перенаправление с https://assertchris.io/auth/twitter на http://localhost:8080/auth/twitter когда заметил, что вы можете использовать разные URL-адреса обратного вызова в интерфейсе Twitter и запросах OAuth. Если вы столкнулись с этой же проблемой, просто используйте поддельный URL-адрес (из реального домена) в интерфейсе Twitter и локальный URL-адрес при выполнении запросов OAuth, которые я собираюсь показать вам…

Щебетать новый экран приложения

Выполнение запросов OAuth

Одна из причин, по которой я выбрал Lumen, была из-за Socialite . Socialite абстрагирует большую часть тяжелой работы, связанной с OAuth, а Guzzle абстрагирует остальных!

Я добавил открытый и секретный ключи для Fitbit и Twitter:

 FITBIT_KEY=... FITBIT_SECRET=... FITBIT_REDIRECT_URI=... TWITTER_KEY=... TWITTER_SECRET=... TWITTER_REDIRECT_URI=... 

Это происходит в .env .

Если бы я .env это приложение в Laravel, мне также нужно было бы изменить app/config/services.php чтобы ссылаться на эти переменные .env .

Затем я установил Socialite и провайдеров Fitbit и Twitter с https://socialiteproviders.github.io :

 composer require laravel/socialite composer require socialiteproviders/fitbit composer require socialiteproviders/twitter 

В части инструкций по установке Socialite рекомендуется добавить поставщика услуг Socialite в список поставщиков услуг в config/app.php . Сторонние поставщики Fitbit и Twitter имеют другого поставщика услуг, который заменяет официального.

Lumen покончил с папкой config , поэтому этот поставщик услуг должен быть зарегистрирован в bootstrap/app.php . Я также раскомментировал класс EventServiceProvider :

 $app->register( App\Providers\EventServiceProvider::class ); $app->register( SocialiteProviders\Manager\ServiceProvider::class ); 

Это происходит в bootstrap/app.php .

Теперь, EventServiceProvider класс EventServiceProvider вернулся в игру, я мог добавить события, которые требовались этим поставщикам OAuth:

 namespace App\Providers; use Laravel\Lumen\Providers\EventServiceProvider as Base; use SocialiteProviders\Manager\SocialiteWasCalled; use SocialiteProviders\Fitbit\FitbitExtendSocialite; use SocialiteProviders\Twitter\TwitterExtendSocialite; class EventServiceProvider extends Base { protected $listen = [ SocialiteWasCalled::class => [ FitbitExtendSocialite::class . "@handle", TwitterExtendSocialite::class . "@handle", ], ]; } 

Это происходит в app/Providers/EventServiceProvider.php .

Со всеми этими настройками я мог начать подключаться к Fitbit и Twitter:

 use Laravel\Socialite\Contracts\Factory; $manager = $app->make(Factory::class); $fitbit = $manager->with("fitbit")->stateless(); $twitter = $manager->with("twitter"); $app->get("/auth/fitbit", function() use ($fitbit) { return $fitbit->redirect(); }); $app->get("/auth/fitbit/callback", function() use ($fitbit) { // use $fitbit->user() }); $app->get("/auth/twitter", function() use ($twitter) { return $twitter->redirect(); }); $app->get("/auth/twitter/callback", function() use ($twitter) { // use $twitter->user() }); 

Это происходит в app/Http/routes.php .

Еще одно, что по умолчанию отключено в Lumen, это управление сессиями. Это имеет смысл, поскольку Lumen предназначен в основном для веб-служб JSON без сохранения состояния, а не для реальных веб-сайтов. Проблема в том, что Socialite использует сессии по умолчанию для OAuth 2. Я могу отключить это с помощью метода stateless()

Каждый провайдер Socialite предоставляет метод redirect() , который я могу вернуть по собственному маршруту. Когда пользователи попадают на этот маршрут, они перенаправляются в службу, с которой я хочу, чтобы они проходили аутентификацию.

Простые dd($fitbit->user()) и dd($twitter->user()) показывают мне, что данные возвращаются на мой локальный сервер, как и ожидалось.

Теперь я могу получать прямые сообщения из Twitter:

 use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Subscriber\Oauth\Oauth1; use Illuminate\Support\Facades\Cache; $app->get("/auth/twitter/callback", function() use ($twitter) { $user = $twitter->user(); Cache::forever("TWITTER_TOKEN", $user->token); Cache::forever("TWITTER_TOKEN_SECRET", $user->tokenSecret); return redirect("/"); }); 

Это происходит в app/Http/routes.php .

Подключение к этим услугам — только первый шаг. Как только они отправят данные обратно в мое приложение, мне нужно прикрепить его к будущим запросам. Лучше получать прямые сообщения по другому маршруту, поэтому сейчас я просто собираюсь хранить их в кеше.

Поставщик кэша по умолчанию — Memcache. Вы можете установить его, если он отсутствует в вашей ОС, или настроить другой драйвер кеша.

Я также использую фасад Laravel, чтобы получить быстрый доступ к базовым классам кэша. По умолчанию они отключены в Lumen, но вы можете раскомментировать $app->withFacades(); в boostrap/app.php чтобы включить их снова.

Извлечение прямых сообщений

Далее я добавлю ссылку на панель инструментов и новый маршрут для получения прямых сообщений:

 <a href="{{ url("twitter/fetch") }}">fetch direct messages</a> 

Это происходит в resources/views/dashboard.blade.php .

 $app->get("twitter/fetch", function() { $middleware = new Oauth1([ "consumer_key" => getenv("TWITTER_KEY"), "consumer_secret" => getenv("TWITTER_SECRET"), "token" => Cache::get("TWITTER_TOKEN"), "token_secret" => Cache::get("TWITTER_TOKEN_SECRET"), ]); $stack = HandlerStack::create(); $stack->push($middleware); $client = new Client([ "base_uri" => "https://api.twitter.com/1.1/", "handler" => $stack, ]); $response = $client->get("direct_messages.json", [ "auth" => "oauth", ]); header("content-type: application/json"); print $response->getBody(); }); 

Это происходит в app/Http/routes.php .

Поскольку у меня есть токен и секрет токена (отправленный нам из Twitter как часть процесса OAuth), мы можем использовать HandlerStack и Oauth1 для прозрачного добавления учетных данных OAuth к запросам, которые я делаю с помощью Guzzle.

Я могу немного изменить это, чтобы записывать текущие прямые сообщения, чтобы я мог знать, когда я получил что-то новое:

 $latest = Cache::get("TWITTER_LATEST", 0); $response = $client->get("direct_messages.json", [ "auth" => "oauth", "query" => [ "since_id" => $latest, ], ]); // header("content-type: application/json"); // print $response->getBody(); $response = (string) $response->getBody(); $response = json_decode($response, true); foreach ($response as $message) { if ($message["id"] > $latest) { $latest = $message["id"]; } } Cache::forever("TWITTER_LATEST", $latest); 

Это происходит в app/Http/routes.php .

Twitter позволяет мне отфильтровывать все прямые сообщения, начиная с указанного идентификатора прямого сообщения, поэтому идея состоит в том, чтобы я перебирал все прямые сообщения и кэшировал самый новый (самый большой) идентификатор. Тогда будущие запросы не будут включать старые прямые сообщения, и я могу установить будильник, если появятся новые сообщения.

Установка тихих будильников

Я хочу сохранить данные токена Fitbit OAuth так же, как я делал для Twitter:

 $app->get("/auth/fitbit/callback", function() use ($fitbit) { $user = $fitbit->user(); Cache::forever("FITBIT_TOKEN", $user->token); Cache::forever("FITBIT_USER_ID", $user->id); return redirect("/"); }); 

Это происходит в app/Http/routes.php .

Теперь, предполагая, что я подключился к Fitbit и Twitter, я могу устанавливать сигналы при появлении новых прямых сообщений:

 if (count($response)) { $token = Cache::get("FITBIT_TOKEN"); $userId = Cache::get("FITBIT_USER_ID"); $client = new Client([ "base_uri" => "https://api.fitbit.com/1/", ]); $response = $client->get("user/-/devices.json", [ "headers" => [ "Authorization" => "Bearer {$token}" ] ]); $response = (string) $response->getBody(); $response = json_decode($response, true); $id = $response[0]["id"]; $endpoint = "user/{$userId}/devices/tracker/{$id}/alarms.json"; $response = $client->post($endpoint, [ "headers" => [ "Authorization" => "Bearer {$token}" ], "form_params" => [ "time" => date("H:iP", strtotime("+1 minute")), "enabled" => "true", "recurring" => "false", "weekDays" => [strtoupper(date("l"))], ], ]); header("content-type: application/json"); print $response->getBody(); } 

Это происходит в app/Http/routes.php .

Обратите внимание, как мне нужно использовать другой тип аутентификации в Fitbit? Fitbit использует OAuth 2 и разрешает заголовки Bearer для токена. Это намного проще, чем требования OAuth 1 в Twitter.

Первый запрос, который я делаю, — это выбор устройств, которые у меня есть в моей учетной записи Fitbit. Если бы у меня было немного больше времени, я мог бы создать список и какой-то выбор, чтобы он был более динамичным.

Получив идентификатор устройства (назначенный Fitbit), я могу создать новый тихий сигнал тревоги. Это требует специального формата даты, и день должен быть таким, какой когда-либо сегодня, в верхнем регистре. Этот бит отладочной информации говорит мне, был ли установлен новый сигнал тревоги или нет. Если нет прямых сообщений, я ничего не вижу.

Вывод

Это был действительно веселый проект для меня. Я немного узнал об ограничениях, накладываемых на новые приложения Lumen, подключение к сервисам OAuth с помощью Socialite и о том, как взаимодействовать с API Fitbit и Twitter.

Мой Fitbit синхронизируется каждые 15 минут (как минимум), а также каждый раз, когда я открываю приложение для iPhone. Возможно, более новые модели Fitbit синхронизируются чаще или имеют какой-то толчок, инициируемый сервером. Это просто ограничение, с которым мне придется жить. Или я мог бы купить Apple Watch.