аутентификация в частных репозиториях github с помощью httr

Я пытаюсь получить доступ к частному репозиторию на Github, используя httr. Я могу сделать это без проблем, если добавлю свой токен github (хранящийся как переменная среды в GITHUB_TOKEN):

httr::GET("https://api.github.com/repos/aammd/miniature-meme/releases/assets/2859674",
                 httr::write_disk("test.rds", overwrite = TRUE),
                 httr::progress("down"),
                 httr::add_headers(Authorization = paste("token", Sys.getenv("GITHUB_TOKEN"))))

Однако, если я попытаюсь указать другой заголовок, я получаю сообщение об ошибке. В этом случае я хочу загрузить двоичный файл, связанный с выпуском («актив» в терминологии github):

httr::GET("https://api.github.com/repos/aammd/miniature-meme/releases/assets/2859674",
                 httr::write_disk("test.rds", overwrite = TRUE),
                 httr::progress("down"),
                 httr::add_headers(Authorization = paste("token", Sys.getenv("GITHUB_TOKEN"))),
                 httr::add_headers(Accept = "application/octet-stream"))


?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidArgument</Code><Message>Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified</Message>

Это только часть сообщения (остальное включает мой токен).

Судя по всему, моя авторизация отправляется дважды! Как я могу предотвратить это? Это связано с httr::handle_pool()

РЕДАКТИРОВАТЬ - информация о соединении

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

-> GET /repos/aammd/miniature-meme/releases/assets/2859674 HTTP/1.1
-> Host: api.github.com
-> User-Agent: libcurl/7.43.0 r-curl/2.3 httr/1.2.1.9000
-> Accept-Encoding: gzip, deflate
-> Authorization: token tttttttt
-> Accept: application/octet-stream
-> 
<- HTTP/1.1 302 Found
<- Server: GitHub.com
<- Date: Tue, 17 Jan 2017 13:28:12 GMT
<- Content-Type: text/html;charset=utf-8
<- Content-Length: 0
<- Status: 302 Found
<- X-RateLimit-Limit: 5000
<- X-RateLimit-Remaining: 4984
<- X-RateLimit-Reset: 1484662101
<- location: https://github-cloud.s3.amazonaws.com/releases/76993567/aee5d0d6-c70a-11e6-9078-b5bee39f9fbc.RDS?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20170117%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170117T132812Z&X-Amz-Expires=300&X-Amz-Signature=ssssssssss&X-Amz-SignedHeaders=host&actor_id=1198242&response-content-disposition=attachment%3B%20filename%3Dff.RDS&response-content-type=application%2Foctet-stream
<- Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
<- Access-Control-Allow-Origin: *
<- Content-Security-Policy: default-src 'none'
<- Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
<- X-Content-Type-Options: nosniff
<- X-Frame-Options: deny
<- X-XSS-Protection: 1; mode=block
<- Vary: Accept-Encoding
<- X-Served-By: 3e3b9690823fb031da84658eb58aa83b
<- X-GitHub-Request-Id: 82782802:6E1B:E9F0BE:587E1BEC
<- 
-> GET /releases/76993567/aee5d0d6-c70a-11e6-9078-b5bee39f9fbc.RDS?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20170117%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170117T132812Z&X-Amz-Expires=300&X-Amz-Signature=sssssssssssssss&X-Amz-SignedHeaders=host&actor_id=1198242&response-content-disposition=attachment%3B%20filename%3Dff.RDS&response-content-type=application%2Foctet-stream HTTP/1.1
-> Host: github-cloud.s3.amazonaws.com
-> User-Agent: libcurl/7.43.0 r-curl/2.3 httr/1.2.1.9000
-> Accept-Encoding: gzip, deflate
-> Authorization: token ttttttttttttt
-> Accept: application/octet-stream
-> 
<- HTTP/1.1 400 Bad Request
<- x-amz-request-id: FA56B3D23B468704
<- x-amz-id-2: 49X1mT5j5BrZ4HApeR/+wb7iVOWA8yn1obrgMoeOy44RH414bo/Ov8AAWSx2baEXO0H/WHX5jK0=
<- Content-Type: application/xml
<- Transfer-Encoding: chunked
<- Date: Tue, 17 Jan 2017 13:28:12 GMT
<- Connection: close
<- Server: AmazonS3
<- 

ГГ тоже не работает

Я создал публичное репо, чтобы проверить эту идею. JSON можно вернуть из API, но не бинарный файл:

# this works fine
gh::gh("https://api.github.com/repos/aammd/test_idea/releases/assets/2998763")

# this does not
gh::gh("https://api.github.com/repos/aammd/test_idea/releases/assets/2998763", .send_headers = c("Accept" = "application/octet-stream"))

Однако wget может сработать

Я нашел суть, которая показывает, как это сделать с помощью wget. Ключевым компонентом, по-видимому, является:

wget -q --auth-no-challenge --header='Accept:application/octet-stream' \
  https://$TOKEN:@api.github.com/repos/$REPO/releases/assets/$asset_id \
-O $2

Однако, если я попытаюсь воспроизвести это в httr::GET, у меня ничего не получится:

auth_url <- sprintf("https://%s:@api.github.com/repos/aammd/miniature-meme/releases/assets/2859674", Sys.getenv("GITHUB_TOKEN"))
httr::GET(auth_url,
          httr::write_disk("test.rds", overwrite = TRUE),
          httr::progress("down"),
          httr::add_headers(Accept = "application/octet-stream"))

Вызов wget из R РАБОТАЕТ, но это решение не совсем удовлетворительное, потому что я не могу гарантировать, что у всех моих пользователей установлено wget (если нет способа сделать это?).

system(sprintf("wget --auth-no-challenge --header='Accept:application/octet-stream' %s -O testwget.rds", auth_url))

вывод wget (обратите внимание на отсутствие -q выше), включенный сюда (опять же, мы надеемся, что токены и подписи отредактированы):

--2017-01-18 13:21:55--  https://ttttt:*password*@api.github.com/repos/aammd/miniature-meme/releases/assets/2859674
Resolving api.github.com... 192.30.253.117, 192.30.253.116
Connecting to api.github.com|192.30.253.117|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://github-cloud.s3.amazonaws.com/releases/76993567/aee5d0d6-c70a-11e6-9078-b5bee39f9fbc.RDS?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20170118%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170118T122156Z&X-Amz-Expires=300&X-Amz-Signature=SSSSSSSS-Amz-SignedHeaders=host&actor_id=1198242&response-content-disposition=attachment%3B%20filename%3Dff.RDS&response-content-type=application%2Foctet-stream [following]
--2017-01-18 13:21:55--  https://github-cloud.s3.amazonaws.com/releases/76993567/aee5d0d6-c70a-11e6-9078-b5bee39f9fbc.RDS?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20170118%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170118T122156Z&X-Amz-Expires=300&X-Amz-Signature=SSSSSSSSSSSS-Amz-SignedHeaders=host&actor_id=1198242&response-content-disposition=attachment%3B%20filename%3Dff.RDS&response-content-type=application%2Foctet-stream
Resolving github-cloud.s3.amazonaws.com... 52.216.226.120
Connecting to github-cloud.s3.amazonaws.com|52.216.226.120|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 682 [application/octet-stream]
Saving to: ‘testwget.rds’

     0K                                                       100% 15.5M=0s

2017-01-18 13:21:56 (15.5 MB/s) - ‘testwget.rds’ saved [682/682]

person AndrewMacDonald    schedule 17.01.2017    source источник
comment
Можете ли вы включить то, что отправляется при добавлении httr::verbose()?   -  person hadley    schedule 17.01.2017
comment
^^ но также редактируя токен (это легко забыть)   -  person hrbrmstr    schedule 17.01.2017
comment
@hadley @hrbrmstr добавлен вывод verbose! надеюсь, мне удалось удалить все приватное (не был уверен в этих подписях)   -  person AndrewMacDonald    schedule 17.01.2017


Ответы (2)


Оказывается, есть два возможных решения этой проблемы!

решение первое: токен как параметр

Как предложил @user7433058, мы действительно можем передать токен в качестве параметра! однако обратите внимание, что мы должны использовать paste0. Это подход, предложенный самим Github в документации по API.

## pass oauth in the url
httr::GET(paste0("https://api.github.com/repos/aammd/miniature-meme/releases/assets/2859674?access_token=", Sys.getenv("GITHUB_TOKEN")),
          httr::write_disk("test.rds", overwrite = TRUE),
          httr::progress("down"),
          httr::add_headers(Accept = "application/octet-stream"))

tt <- readRDS("test.rds")

Решение второе: переспросить

Другое решение — сделать запрос в первый раз, затем извлечь URL-адрес и использовать его для отправки второго запроса. Поскольку проблема вызвана двойной отправкой информации об авторизации — один раз в URL-адресе и один раз в заголовке — мы можем избежать проблемы, используя только URL-адрес.

## alternatively, get the query url (containing signature) from the (failed) html request made the first time
firsttry <- httr::GET("https://api.github.com/repos/aammd/miniature-meme/releases/assets/2859674",
                      httr::add_headers(Authorization = paste("token", Sys.getenv("GITHUB_TOKEN")),
                                        Accept = "application/octet-stream"))

httr::GET(firsttry$url, httr::write_disk("test.rds", overwrite = TRUE),
          httr::write_disk("test2.rds", overwrite = TRUE),
          httr::progress("down"),
          httr::add_headers(Accept = "application/octet-stream"))

tt2 <- readRDS("test2.rds")

Это, я полагаю, немного менее эффективно (всего 3 запроса вместо 2). Однако, поскольку только первый запрос относится к фактическому API-интерфейсу github, он считается только 1 на этапе ограничения скорости.

небольшое уточнение: нет редиректа с httr

Мы можем сделать только 2, а не 3 HTTP-запроса, если вы скажете httr не использовать редиректы. Для этого используйте httr::config(followlocation = FALSE) в первом из двух запросов (т.е. чтобы получить firsttry)

person AndrewMacDonald    schedule 18.01.2017

Попробуйте отправить токен аутентификации в качестве параметра запроса вместо заголовка аутентификации. Таким образом, когда Oauth GitHub перенаправит вас, он лишит исходный токен, а параметр X-Amz-Algorithm останется выполнять свою работу.

httr::GET(paste("https://api.github.com/repos/aammd/miniature-meme/releases/assets/2859674?access_token=", Sys.getenv("GITHUB_TOKEN")),
             httr::write_disk("test.rds", overwrite = TRUE),
             httr::progress("down"))
person user7433058    schedule 17.01.2017
comment
К сожалению, это просто привело к появлению нового сообщения об ошибке :S . однако это указало мне на частичное решение выше, используя wget - person AndrewMacDonald; 18.01.2017
comment
Я заставил его работать! оказывается, должно быть paste0. Оказывается, на самом деле есть два способа решить эту проблему (см. Мой ответ ниже). Если с вами все в порядке, я поддержу ваш ответ, @user7433058, но приму свой собственный для полноты картины. - person AndrewMacDonald; 18.01.2017