Журнал LinuxFormat - перейти на главную

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_statusbase=”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} Уда­ля­ет са­мую длин­ную най­ден­ную под­стро­ку с кон­ца стро­ки.
Персональные инструменты
купить
подписаться
Яндекс.Метрика