- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF137:bash
Материал из Linuxformat.
- Bash Извернитесь в Linux, сэкономив время и упростив себе жизнь
Содержание |
Bash: В Твиттер через OAuth
- Непробиваемый OAuth теперь используется в Твиттере по умолчанию, и Ник Вейч спешит на выручку, создавая Bash-клиент для обуздания нового API.
Bash. Рэйчел Проберт |
---|
Bash. Ник Вейч |
---|
|
Вероятно, вы уже видели консольные однострочники для обновления статуса в Твиттере через Curl – аккуратное, технологичное и удобное средство для запуска ботов на серверах и встраиваемых устройствах. Но так было раньше. OAuthoкалипсис грянул 16 августа: из API Твиттера исключили простую текстовую авторизацию, заменив на OAuth, более безопасный, но также более сложный протокол. Страшно? Да, но не аж жуть: задача нашего урока – возродить Твитосферу в командной строке, создав Bash-клиент, который проведет нас через мутные воды OAuth.
Ох… Auth
Прежде чем мы действительно сможем разместить что-то в Твиттере, необходимо настроить авторизацию OAuth. Она работает так: имеется жетон [token] и ключ [key] пользователя, выданные при регистрации клиента в API Твиттер. Берем жетон, добавляем запрос и подписываем все это ключом, затем отправляем в API. API возвращает нам новый жетон, и мы используем его для перехода к URL, где пользователь сможет подтвердить доступ и получить PIN. Затем мы используем временный жетон и ключ вместе с PIN для создания и подписи следующего сообщения для получения постоянного жетона и ключа. К счастью, полученный жетон бессрочный (по крайней мере, в Твиттере).
Вследствие сложности процесса, данный учебник разбит на две части – получение жетона и его использование для отправки сообщений. Полный скрипт для первой части находится на LXFDVD, но мы продолжим так, словно вбиваем команды в оболочке обычным способом (что можно делать, просто копируя их из файла на диске и вставляя).
Время признаний
Итак, буду с вами откровенен. Две вещи в Bash сделать непросто (разве что вы умнее меня). Прежде всего это кодирование URL, то есть отображение обычных строк в web-безопасной манере, с заменой неалфавитных символов через %xx: например, %20 для пробела. Да, это делается заменой подстрок, но задача тем не менее непростая.
Однако я разыскал неплохой компромисс (благодаря http://stakface.com/nuggets). Он использует Perl, и, как всегда с этим языком, для его запуска, похоже, необходимо принести в жертву барана или нечто подобное:
perl -p -e ‘s/([^A-Za-z0-9-._~])/sprintf(“%%%02X”, ord($1))/seg’
А вторая вещь? Ну, она не столь неприятная. Чтобы реализовать OAuth, необходим способ генерировать подписи. Для этого нет простой команды или Bash-эквивалента, но можно воспользоваться инструментарием OpenSSL. Поскольку это штука базовая, я вряд ли ошибусь, предположив, что у всех он уже имеется. И если мы введем эту часть вспомогательного кода (и простой фрагмент для генерации временных меток), то получим
pencode () { echo -n “$1” | perl -p -e ‘s/([^A-Za-z0-9-._~])/ sprintf(“%%%02X”, ord($1))/seg’ } hmacsign () { # базовая строка, секретный ключ клиента, ключ жетона local key=”$2&$3” echo -n “$1” | openssl dgst -sha1 -binary -hmac “$key” | base64 } timestamp () { echo -n “$(date +%s)” }
На LXFDVD есть файл с этим кодом (по имени functions.sh) – скопируйте его содержимое и вставьте в оболочку, если желаете следовать уроку: уж больно в нем много загадочных символов и конструкций для набора вручную.
Так, с этим мы разобрались; продолжим с получением жетона. Для создания последующего кода понадобятся учетная запись Твиттера. Войдите в нее, затем перейдите на https://twitter.com/apps и зарегистрируйте ваше приложение. Здесь важно в конце записать с экрана API-ключ/жетон пользователя и секретный код – оба они потребуются для включения в скрипт, который мы намерены написать:
oauth_consumer_key=’gzKezO9rJZQwE3imVhw’ oauth_consumer_secret=’vdgf3EIrSs9wvUuUFQdAG3MHIsHMdlh4twfTVPzbk’ oauth_method=’HMAC-SHA1’ oauth_version=’1.0’ oauth_token_secret=’’ url_request=’https://api.twitter.com/oauth/request_token’ url_access=’https://api.twitter.com/oauth/access_token’ url_authorize=’https://api.twitter.com/oauth/authorize’
Регистрация нашего клиента на сайте API Твиттера обеспечила нам ключ [consumer key] и секрет пользователя [consumer secret]. Будем придерживаться этих названий, потому что они используются при создании заголовков и прочего, и это упрощает понимание. URL’ы – просто для удобства обращения к различным точкам сайта. Далее идет пара специальных переменных, nonce и timestamp [метка времени]:
oauth_nonce=$RANDOM$RANDOM$RANDOM oauth_timestamp=$( timestamp )
Метка времени – обычное для Unix количество секунд с начала эпохи, которое мы получаем от вспомогательной функции. ‘nonce’ происходит от «number used once» [единожды используемое число]; оно должно быть уникальной ID-строкой. Создание и отслеживание списка уникальных чисел достаточно проблематично, так что мы просто объединили вместе несколько встроенных в Bash случайных чисел, что дает нам 10^15 степени возможных вариантов – авось, достаточно.
Метка времени проверяется на другом конце, и если она «устарела», то ваш запрос не обрабатывается. Обычно «за все про все» вам дается минут 10, так что оставшийся код набирайте в умеренном темпе. Если вам необходимо пересоздать его, придется пересоздать и базовую строку (base string):
base1=”oauth_callback=oob&” base2=”oauth_consumer_key=$oauth_consumer_key&oauth_nonce=$oauth_nonce&” base3=”oauth_signature_method=$oauth_method&oauth_timestamp=$oauth_timestamp&” base4=”oauth_version=$oauth_version” base=$base1$base2$base3$base4 base=”POST&”$(pencode “$url_request”)$”&”$(pencode “$base”)
Базовая строка – это список требуемых параметров, URL, куда вы хотите отправить их, и сам метод отправки (POST). По правилам OAuth, параметры имеют форму ключ=значение и идут в алфавитном порядке. Поскольку мы знаем, что представляют собой ключи, можно просто перечислить их вручную и обойтись без сортировки. Мы также используем вспомогательную функцию для URL-кодировки запроса.
Базовая строка представляет собой полную запись запроса, которая затем может быть подписана. На стороне сервера, запрос можно преобразовать обратно в базовую строку, подписать и проверить, совпадают ли две подписи.
Теперь создадим подпись и URL-закодируем ее при помощи определенной ранее вспомогательной функции:
signed=$( hmacsign $base $oauth_consumer_secret ‘’) signed=$( pencode $signed )
Обзаведясь подписью, соберем заголовок нашего запроса, включающий и все использованные нами параметры, и подпись. На сей раз порядок уже не важен. Текст заголовка обязан начинаться с его определения, и, в случае OAuth, следует также указать «область» [realm], на которую он ссылается, что может быть просто доменным именем.
header1=”Authorization: OAuth realm=\”http://api.twitter.com/\”, “ header2=”oauth_consumer_key=\”$oauth_consumer_key\”, “ header3=”oauth_signature_method=\”HMAC-SHA1\”, “ header4=”oauth_version=\”$oauth_version\”, “ header5=”oauth_nonce=\”$oauth_nonce\”, “ header6=”oauth_timestamp=\”$oauth_timestamp\”, “ header7=”oauth_signature=\”$signed\”, “ header8=”oauth_callback=\”oob\”” header=$header1$header2$header3$header4$header5$header6$header7$header8
Мы подставили в конец еще одну штуковину – обратный вызов [callback]. Для клиентов на базе web тут бы нужен URL, куда Твиттер будет возвращать результаты. Нам-то этого не надо, но для порядку проставим здесь oob (сокращение от «out of bounds» [вне границ]). Для создания строк есть способы и получше, но их мы там и сям используем далее.
Итак, у нас есть все необходимые части, и можно использовать Curl для посылки информации. Отметим, что здесь нет тела сообщения – вместо него вставим пустую строку.
result=$(curl -s -d ‘’ -H “$header” “$url_request”) token=${result%%&*} secret=${result%&*} secret=${secret#*&} oauth_token_secret=${secret#*=} oauth_token=${token#*=}
Переменная $result принимает данные, приходящие от сайта; в данном случае это длинная строка, содержащая временный жетон и секретный ключ. Последние команды выделяют эти части. И вновь, вы могли бы сделать это более очевидным способом – при помощи, например, sed; но зато так мы не покидаем Bash. А теперь создадим адрес ссылки:
echo ${url_authorize}?$token
Эта команда должна выдать URL, который можно потом вставить в браузер. Войдите в Твиттер, и вы увидите экран с магическим числом. Нам следует упаковать это число вместе с жетонами и отправить на серверы Твиттер другой запрос. Прежде всего введите PIN и сохраните его в виде переменной:
oauth_verifier=<???????>
Затем необходимо создать свежее nonce и новую метку времени – если позабыть это сделать, сервер вернет ошибку, а сообщения об ошибках часто не слишком информативны (не то чтобы «ты ошибся, вали отсюда», но немногим полезнее).
oauth_nonce=$RANDOM$RANDOM$RANDOM oauth_timestamp=$( timestamp )
Далее необходимо пересоздать базовую строку. Кроме новых значений временной метки и nonce, у нас сейчас есть временный жетон, секрет и PIN, переписанный с сайта – все это надо добавить.
# генерация новой базовой строки base2=”oauth_consumer_key=$oauth_consumer_key&” base3=”oauth_consumer_secret=$oauth_consumer_secret&” base4=”oauth_nonce=$oauth_nonce&” base5=”oauth_signature_method=$oauth_method&oauth_timestamp=$oauth_timestamp&” base6=”oauth_token=$oauth_token&oauth_token_secret=$oauth_token_secret&” base7=”oauth_verifier=$oauth_verifier&oauth_version=$oauth_version” base=$base1$base2$base3$base4$base5$base6$base7 base=”POST&”$(pencode “$url_access”)$”&”$(pencode “$base”)
Мы можем повторно использовать по крайней мере одну строку из предыдущей версии этого кода, и вот одна из причин создания $base «по разделениям»: это здорово экономит на вводе. Теперь можно генерировать подпись – так же, как и ранее, только в этот раз у нас для нее есть два ключа. Вспомогательная функция, которую мы написали, объединит ключи и использует их оба для подписи текста.
signed=$( hmacsign $base $oauth_consumer_secret $oauth_token_secret) signed=$( pencode $signed )
Пора браться за заголовок. И вновь, можно повторно использовать участки предыдущего кода, но, конечно, обновив подпись, временную метку и прочее:
header5=”oauth_nonce=\”$oauth_nonce\”, “ header6=”oauth_timestamp=\”$oauth_timestamp\”, “ header7=”oauth_signature=\”$signed\”, oauth_token=\”$oauth_token\”, “ header8=”oauth_callback=\”oob\”, oauth_verifier=$oauth_verifier” header=$header1$header2$header3$header4$header5$header6$header7$header8 result=$(curl -s -d ‘’ -H “$header” “$url_access”)
Мы поместили ответ в переменную для дальнейшей обработки. В данном случае это длинный текст, содержащий различные пары ключ=значение для имени пользователя и ID пользователя, а также постоянный жетон и секрет. Они объединяются в длинную строку с помощью символа &, и мы вновь можем использовать встроенную в Bash магию подстановки строк для выделения желаемых кусков.
username=${result##*=} token=${result%%&*} token=${token##*=} secret=${result##*secret=} secret=${secret%%&*} id=${result##*id=} id=${id%%&*}
Далее вы можете пожелать вывести все детали в удобоваримом виде (скажем, командой echo) и сохранить их в безопасном месте. Как упоминалось ранее, некоторые сайты с OAuth требуют периодического обновления вашего жетона, но многие, включая Твиттер, в настоящее время используют авторизацию с постоянным жетоном (если только пользователь не отменит это на сайте). То есть первую часть нашего кода, которую мы как раз завершили, вероятнее всего, потребуется запустить только один раз – просто запомните где-нибудь свои жетон и ключ и передавайте их любому скрипту.
Твит-скрипт
Имея безопасный жетон, мы теперь можем передать его нашему клиенту командной строки (просто подставьте ваши значения в присваивание первым двум переменным). Здесь выполняются схожие действия: необходимо опять создать из параметров базовую строку, включая текст обновления статуса, и перед отсылкой подписать все это двумя ключами. Вот полный листинг:
#!/bin/bash oauth_consumer_key=’<ваш ключ>’ oauth_consumer_secret=’<ваш секрет>’ oauth_method=’HMAC-SHA1’ oauth_version=’1.0’ oauth_token=’<токен из части 1>’ oauth_token_secret=’<ключ токена из части 1>’ url_update=’http://api.twitter.com/1/statuses/update.xml’ #Вспомогательная функция pencode () { echo -n “$1” | perl -p -e ‘s/([^A-Za-z0-9-._~])/ sprintf(“%%%02X”, ord($1))/seg’ } hmacsign () { # базовая строка, секретный ключ клиента, ключ жетона local key=”$2&$3” echo -n “$1” | openssl dgst -sha1 -binary -hmac “$key” | base64 } timestamp () { echo -n “$(date +%s)” } new_status=”$@” new_status=$(pencode “$new_status”) # генерация данных и временной метки oauth_nonce=$RANDOM$RANDOM$RANDOM oauth_timestamp=$( timestamp ) # создаем базовую строку base=”oauth_consumer_key=$oauth_consumer_key&oauth_ nonce=$oauth_nonce&\ oauth_signature_method=$oauth_method&oauth_ timestamp=$oauth_timestamp&\ oauth_token=$oauth_token&oauth_version=$oauth_version&\ status=$new_status” base=”POST&”$(pencode “$url_update”)$”&”$(pencode “$base”) # подписываем ее signed=$( hmacsign $base $oauth_consumer_secret $oauth_token_secret) signed=$( pencode $signed ) # создаем заголовок header=”Authorization: OAuth realm=\”http://api.twitter.com/\”, \ oauth_consumer_key=\”$oauth_consumer_key\”, \ oauth_signature_method=\”HMAC-SHA1\”, \ oauth_version=\”$oauth_version\”, \ oauth_nonce=\”$oauth_nonce\”, \ oauth_token=\”$oauth_token\”, \ oauth_timestamp=\”$oauth_timestamp\”, \ oauth_signature=\”$signed\”” body=”status=$new_status” # отправляем result=$(curl -s -d “$body” -H “$header” “$url_update”) echo $result
При установке верных прав доступа и включения пути к этому файлу в переменную среды, вы сможете твиттить из командной строки! На данном этапе наш клиент выдает XML-ответ от сервера в качестве инструмента отладки и обратной связи – естественно, за ненадобностью вы можете удалить выражения echo.
Движемся дальше
Данная команда не содержит каких-либо чудес вроде, например, собственно чтения Твиттера, ответа на сообщения или обновления строки местоположения. Зато она выполняет тяжелую работу – а именно, авторизацию вашего клиента на Твиттере с использованием OAuth. Вы можете также пожелать сохранить жетоны вне скрипта, чтобы все ваши пользователи могли применять их в виде конфигурационного файла.
Хотя данная задача решается определенно сложнее, чем в PHP, Python или Perl, я надеюсь, что вы убедились в немалой мощи самого Bash и при минимуме требуемых ресурсов сможете внедрить это на сервере или сетевом устройстве.
Bash пережевывает строки
Различных способов обработки строк имеется множество, но есть важные и полезные инструменты, встроенные прямо в Bash. Вот некоторые выражения:
- ${string#sub} Удаляет самую короткую найденную подстроку [sub] от начала строки [string].
- ${string##substring} Удаляет самую длинную найденную подстроку [substring] от начала строки.
- ${string%sub} Удаляет самую короткую найденную подстроку с конца строки.
- ${string%%substring} Удаляет самую длинную найденную подстроку с конца строки.