10
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

sharepointにファイルをアップロードする

Posted at

経緯

office365のsharepointへ、サーバからファイルをアップロードする要件が必要になってきました。
webdav使えば簡単にアップロードできるかなーと安易に考えていたのですが、UNIX系OSのwebdavからつなぐ方法が見つからず……。

結局、curl コマンド ライン ツールで SharePoint Online REST API を呼び出すを参考にrubyで専用のスクリプトを組みました。
REST APIの認証使おうとしたんですが、ブラウザ経由しない分、こちらの方が使い安いですね。

やっていること

  1. SharePoint Online へリクエストを行い RpsContextCookie を取得
  2. Office 365 の STS へ SAML リクエストを行い、BinarySecurityTokenを取得
  3. BinarySecurityTokenを使ってSharePoint Online へ認証リクエストを行ない、Authentication Cookieを取得
  4. /_api/contextinfoにアクセスしてFormDigestValueを取得
  5. FormDigestValueを使って、ファイルをPOSTする

1-3はcurl コマンド ライン ツールで SharePoint Online REST API を呼び出すにある通りです。
最初、FormDigestValueが足りなくて、403系のエラーがでまくって、ドツボにはまりました。

作ったスクリプト

sharepoint_upload.rb
#!/usr/bin/env ruby

# SHAREPOINT にファイルアップロードするためのスクリプトです
# 環境変数に予め以下の3つを設定ください
# SHAREPOINT_DOMAIN
# USERNAME
# PASSWORD
#
# 以下の様なコマンドで利用を想定しています
# ./sharepoint_upload.rb local_filepath upload_path upload_filename

require "base64"
require "net/https"
require "pp"
require "uri"
require "json"

class SharepointUpload

  SHAREPOINT_DOMAIN = ENV["SHAREPOINT_DOMAIN"]
  USERNAME = ENV["USERNAME"]
  PASSWORD = ENV["PASSWORD"]

  def get_cookie(response)
    cookie = {}
    response.get_fields("Set-Cookie").each do |str|
      k,v = str[0...str.index(";")].split("=", 2)
      cookie[k] = v
    end
    cookie
  end

  @cookie = nil
  def login_cookie_get
    return @cookie if @cookie
    http = Net::HTTP.new(SHAREPOINT_DOMAIN, 443)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    response = http.start{|https|
      http.post("/_layouts/Authenticate.aspx?Source=", "")
    }

    cookie = get_cookie(response)
    xml = "<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:a='http://www.w3.org/2005/08/addressing' xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'><s:Header><a:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand='1'>https://login.microsoftonline.com/extSTS.srf</a:To><o:Security s:mustUnderstand='1' xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'><o:UsernameToken><o:Username>#{USERNAME}</o:Username><o:Password>#{PASSWORD}</o:Password></o:UsernameToken></o:Security></s:Header><s:Body><t:RequestSecurityToken xmlns:t='http://schemas.xmlsoap.org/ws/2005/02/trust'><wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'><a:EndpointReference><a:Address>https://tenant.sharepoint.com/</a:Address></a:EndpointReference></wsp:AppliesTo><t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType></t:RequestSecurityToken></s:Body></s:Envelope>"
    http = Net::HTTP.new("login.microsoftonline.com", 443)
    http.use_ssl = true
    req = Net::HTTP::Post.new("/extSTS.srf")
    req["Cookie"] = cookie.map{|k,v| "#{k}=#{v}" }.join(";")
    req.body = xml
    response = http.request(req)
    response.body =~ /wsse:BinarySecurityToken Id="Compact0">([^<]*)</
    binary_security_token = $1

    http = Net::HTTP.new(SHAREPOINT_DOMAIN, 443)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    req = Net::HTTP::Post.new("/_forms/default.aspx?wa=wsignin1.0")
    req.body = binary_security_token
    response = http.request(req)
    @cookie = get_cookie(response)
  end

  def post(path, headers ={}, body = nil)
    http = Net::HTTP.new(SHAREPOINT_DOMAIN, 443)
    http.use_ssl = true

    cookie = login_cookie_get
    req = Net::HTTP::Post.new(path)
    headers.merge({"Cookie" => cookie.map{|k,v| "#{k}=#{v}" }.join("; ")}).each do | k, v|
      req[k] = v
    end
    req.body = body if body
    http.request(req)
  end

  def form_digest_value
    response = post("/_api/contextinfo", "Accept" => "application/json;")
    JSON.parse(response.body)["FormDigestValue"]
  end

  def initialize(upload_file, upload_path, upload_filename)
    unless File.exists?(upload_file)
      puts "File not found"
      return
    end
    path = "/_api/web/GetFolderByServerRelativeUrl('#{URI.encode(upload_path)}')/Files/add(url='#{URI.encode(File.basename(upload_filename))}',overwrite=true)"

    body = open(upload_file).read
    header = {"X-RequestDigest" => form_digest_value, "content-length" => body.length}
    response = post(path, header, body)
    puts response.body
  end
end

upload_file = ARGV[0]
upload_path = ARGV[1]
upload_filename = ARGV[2]

SharepointUpload.new(upload_file, upload_path, upload_filename)
10
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?