sharepointへcurlでファイルをuploadする

sharepointcurlでファイルを upload する

ってなことをやって見ました。sharepointのRESTAPIをcurlで利用します。SAMLに認証ってややこしい。
この記事で記述されている方法は
williamvanstrien.blogspot.com
ここに書かれている内容を元にしています。

プレイヤの整理

認証を通すためと、ファイルを突っ込むためにプレイヤは4人でてきます。

  1. クライアント
  2. カスタムSTS
  3. O356の認証(login.microsoftonline.com)
  4. エンドポイント(tenant.sharepoint.com)

カスタムSTS( Security Token Service) は利用形態によってはないところもあるかも。

手順

手順はこんなかんじ。

  • クライアントからO365へ customSTS のURL を聞く
  • 上記で帰ってきたレスポンスから customSTS のURLを抜きだす。
  • customSTS へ SAML;assersion をリクエストする。
  • customSTSから帰ってきた SAML:assersion を使って、endpointにSPOIDCRLクッキー値を得る。
  • endpoint から帰ってきた SPOIDCRLクッキー値を使って endpointへREST APIを発行する。

図で示すとこんなです。
f:id:rougeref:20200416132657j:plain

じゃあ、手順を具体的に。

認証

まずは認証手順から。最終的にはSPOIDCRLクッキー値を得ることが目的です。

カスタムSTSURIを得る

これは利用している環境にdependですが、sharepointで認証するときにActiveDIretoryと連携していたりする環境だと該当するかと。
こんなリクエストをcurlで投げます。loginのところは自分が利用している環境に合わせて変えてください。

$ curl -X POST -H "Content-Type: application/x-www-form-urlencoded"  \
        -d "login=hoge@example.com&xml-1" \
        https://login.microsoft.com/GetUserRealm.srf -w "\n"

XML形式でレスポンスが帰ってきます。
<STSAuthURL>〜</STSAuthURL>タグのところがカスタムSTSのURLです。
sedで抜き出すならこんな。

STSURL=$(上記のcurlコマンド | sed -n 's:.*<STSAuthURL>\(.*\)</STSAuthURL>.*:\1:p'

例として、このURIhttps://adfs.ad.example.com だったとします。

SAML:assersionリクエスト用のXMLファイルを作成する。

上記手順で取得したカスタムSTSに対してSAM:assertionをリクエストします。リクエストするためにXMLファイルが必要ですが、以下手順で作成します。
以下の内容をfile1として保存。
<wsse:Username>のところと<wsse:Password>のところは自分の認証情報へ変更してください。

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope
    xmlns:s="http://www.w3.org/2003/05/soap-envelope"
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsa="http://www.w3.org/2005/08/addressing"
    xmlns:wssc="http://schemas.xmlsoap.org/ws/2005/02/sc"
    xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust">
    <s:Header>
        <wsa:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
        <wsa:To s:mustUnderstand="1">https://<sts_address>/adfs/services/trust/2005/usernamemixed</wsa:To>
        <wsa:MessageID>b07da3ec-9824-46a5-a102-2329e0c5f63f</wsa:MessageID>
        <ps:AuthInfo
            xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="PPAuthInfo">
            <ps:HostingApp>Managed IDCRL</ps:HostingApp>
            <ps:BinaryVersion>6</ps:BinaryVersion>
            <ps:UIVersion>1</ps:UIVersion>
            <ps:Cookies></ps:Cookies>
            <ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>
        </ps:AuthInfo>
        <wsse:Security>
            <wsse:UsernameToken wsu:Id="user">
                <wsse:Username>hoge@example.com</wsse:Username>
                <wsse:Password>password</wsse:Password>
            </wsse:UsernameToken>
            <wsu:Timestamp Id="Timestamp">

以下の内容をfile2として保存。

            </wsu:Timestamp>
        </wsse:Security>
    </s:Header>
    <s:Body>
        <wst:RequestSecurityToken Id="RST0">
            <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
            <wsp:AppliesTo>
                <wsa:EndpointReference>
                    <wsa:Address>urn:federation:MicrosoftOnline</wsa:Address>
                </wsa:EndpointReference>
            </wsp:AppliesTo>
            <wst:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</wst:KeyType>
        </wst:RequestSecurityToken>
    </s:Body>
</s:Envelope>

file1とfile2の間に以下のコマンドの出力結果をはさみます。

echo "<wsu:Created>`date -u +'%Y-%m-%dT%H:%M:%SZ'`</wsu:Created>" 
echo "<wsu:Expires>`date -u +'%Y-%m-%dT%H:%M:%SZ' --date='-15 minutes ago'`</wsu:Expires>" 

こんなかんじで。

$ cat file1 > saml_request.xml
$ echo "<wsu:Created>`date -u +'%Y-%m-%dT%H:%M:%SZ'`</wsu:Created>"  >> saml_request.xml
$ echo "<wsu:Expires>`date -u +'%Y-%m-%dT%H:%M:%SZ' --date='-15 minutes ago'`</wsu:Expires>"  >> saml_request.xml
$ cat file2 >>  saml_request.xml
customSTSへリクエス

SMAL:assertionを要求します。

$ curl -X POST -H "Content-Type: application/soap+xml; charset=utf-8" -d "@saml_request.xml" https://adfs.ad.example.com -w "\n"

これもXMLでレスポンスがあります。
<saml:Assertion>タグのところがSAML:assertionです。
タグつきで保存しておきます。sedで抜き出すならこんな。

$ SAML=$(上記curl | sed 's/^.*\(<saml:Assertion.*saml:Assertion>\).*$/\1/' )
バイナリセキュリティトークン用のXMLファイルを作成

上記でgetしたSAML;assertionを使ってバイナリセキュリティトークンを得ます。
セキュリティトークンをリクエストするためのSAML:assertionが入ったXMLファイルを用意します。

以下の内容をfile3として保存。

<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope
    xmlns:S="http://www.w3.org/2003/05/soap-envelope"
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsa="http://www.w3.org/2005/08/addressing"
    xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust">
    <S:Header>
        <wsa:Action S:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
        <wsa:To S:mustUnderstand="1">https://login.microsoftonline.com/rst2.srf</wsa:To>
        <ps:AuthInfo
            xmlns:ps="http://schemas.microsoft.com/LiveID/SoapServices/v1" Id="PPAuthInfo">
            <ps:BinaryVersion>5</ps:BinaryVersion>
            <ps:HostingApp>Managed IDCRL</ps:HostingApp>
        </ps:AuthInfo>
        <wsse:Security>

以下の内容をfile4として保存。

        </wsse:Security>
    </S:Header>
    <S:Body>
        <wst:RequestSecurityToken xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust" Id="RST0">
            <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
            <wsp:AppliesTo>
                <wsa:EndpointReference>
                    <wsa:Address>sharepoint.com</wsa:Address>
                </wsa:EndpointReference>
            </wsp:AppliesTo>
            <wsp:PolicyReference URI="MBI"></wsp:PolicyReference>
        </wst:RequestSecurityToken>
    </S:Body>
</S:Envelope>

file3とSAML:assertion値とfile4を連結します。
こんな。

$ cat file3 > bst.xml
$ 上記curl | sed 's/^.*\(<saml:Assertion.*saml:Assertion>\).*$/\1/'  >> bst.xml
$ cat file4 >> bst.xml
バイナリセキュリティトークンを得る

上記手順で作成したXMLファイルを利用してバイナリセキュリティトークンを得ます。

$ curl -X POST -H "Content-Type: application/soap+xml; charset=utf-8" \
        -d "@bst.xml" https://login.microsoftonline.com/RST2.srf -w "\n"

レスポンスがXMLで帰ってきます。
<wsse:BinarySecurityToken> タグのところがバイナリセキュリティトークンです。
こんどはタグはいりません。中身だけ保存。

SPOIDCRLクッキー値を得る

やっと欲しいものが入手できる。上記手順でgetしたバイナリセキュリティトークンを使って、エンドポイントに対してSPOIDCTLクッキー値をリクエストします。

$ curl -i -X GET -H "Authorization: BPOSIDCRL ${BinarySecurityToken}" \
        -H "X-IDCRL_ACCEPTED: t" 
        -H "User-Agent:" 
        https://example.sharepoint.com/_vti_bin/idcrl.svc/

このsvcの後のスラッシュがないとエラーになったりします。なんだその仕様。
このレスポンスのヘッダに"set-cookie: SPOIDCRL"という行があるので、その行をそのままどこかへ保存。
内容はこんな感じなはず。

Set-Cookie: SPOIDCRL=(なんだかアルファベットと数字がたくさん); path=/; secure; HttpOnly

SPOIDCRLクッキー値を使ってfileをuploadする。

こんな感じです。

$ curl -b spoidcrl.txt -T path_to_file https://example.sharepoint.com/sites/fuga

一番上で紹介しているサイトではきっちりとスクリプトに組んでくれていますので、それを利用するのが吉かも。
ところどころ間違ってたり、(/がないとか、curl -iオプションがないとか)プロクシが不要だったりすので改変する必要があるでしょう。