LINEWORKS API 2.0 C# で AccessToken を得る方法 [Newtonsoft.Json を使わない版]

 NET 5.0 ~ は、「ImportFromPem」というメソッドが備わっているため、LINEWORKS が提供している PEM 形式の private-key をそのまま読み込める。

System.Collections.Generic.Dictionary<string, string> GetAccessToken(System.IO.FileInfo privateKey, string service_account, string client_id, string client_secret, params string[] scopes)
{
    using (var rsa = System.Security.Cryptography.RSA.Create())
    {
        rsa.ImportFromPem(System.IO.File.ReadAllText(privateKey.FullName));

        var descriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor
        {
            Issuer = client_id,
            Claims = new System.Collections.Generic.Dictionary<string, object>() { ["sub"] = service_account },
            SigningCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(new Microsoft.IdentityModel.Tokens.RsaSecurityKey(rsa), "RS256"),
            IssuedAt = System.DateTime.UtcNow,
            Expires = System.DateTime.UtcNow.AddMinutes(60),
        };

        var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
        var pv = new System.Collections.Specialized.NameValueCollection()
        {
            ["assertion"] = handler.WriteToken(handler.CreateJwtSecurityToken(descriptor)),
            ["grant_type"] = @"urn:ietf:params:oauth:grant-type:jwt-bearer",
            ["client_id"] = client_id,
            ["client_secret"] = client_secret,
            ["scope"] = string.Join(",", scopes),
        };

        using (var wc = new System.Net.WebClient())
        {
            var resText = System.Text.Encoding.ASCII.GetString(wc.UploadValues(@"https://auth.worksmobile.com/oauth2/v2.0/token", pv));
            return System.Text.Json.JsonSerializer.Deserialize<System.Collections.Generic.Dictionary<string, string>>(resText);
        }
    }
}

※「System.IdentityModel.Tokens.Jwt」を nuget しておくこと


 NET Framework ~ 4.8 だと XML 形式の private-key しか読めないので、いったん NET 5.0 ~ を使って XML に変換するプログラムを書いて変換しておく。

void PEM2XML(System.IO.FileInfo pemFile)
{
    using (var rsa = System.Security.Cryptography.RSA.Create())
    {
        rsa.ImportFromPem(File.ReadAllText(pemFile.FullName));
        File.WriteAllText(pemFile.FullName + ".xml", rsa.ToXmlString(true));
    }
}

 そのうえで、「ImportFromPem」の代わりに「FromXmlString」を使う。

System.Collections.Generic.Dictionary<string, string> GetAccessToken(System.IO.FileInfo privateKey, string service_account, string client_id, string client_secret, params string[] scopes)
{
    using (var rsa = System.Security.Cryptography.RSA.Create())
    {
        rsa.FromXmlString(System.IO.File.ReadAllText(privateKey.FullName));

        var descriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor
        {
            Issuer = client_id,
            Claims = new System.Collections.Generic.Dictionary<string, object>() { ["sub"] = service_account },
            SigningCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(new Microsoft.IdentityModel.Tokens.RsaSecurityKey(rsa), "RS256"),
            IssuedAt = System.DateTime.UtcNow,
            Expires = System.DateTime.UtcNow.AddMinutes(60),
        };

        var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
        var pv = new System.Collections.Specialized.NameValueCollection()
        {
            ["assertion"] = handler.WriteToken(handler.CreateJwtSecurityToken(descriptor)),
            ["grant_type"] = @"urn:ietf:params:oauth:grant-type:jwt-bearer",
            ["client_id"] = client_id,
            ["client_secret"] = client_secret,
            ["scope"] = string.Join(",", scopes),
        };

        using (var wc = new System.Net.WebClient())
        {
            var resText = System.Text.Encoding.ASCII.GetString(wc.UploadValues(@"https://auth.worksmobile.com/oauth2/v2.0/token", pv));
            return System.Text.Json.JsonSerializer.Deserialize<System.Collections.Generic.Dictionary<string, string>>(resText);
        }
    }
}

M5Stack CAT-M Unit にpovo を挿し、インターネットから画像をダウンロードし画面表示/印刷

http://dl.ftrans.etr.jp/?897f9b44a210470ca7448f51f592db27a0164fe5 http://dl.ftrans.etr.jp/?0a55cc7c42974b0494a1354558d2663e17e8f3dd


 ボードマネージャの選択が M5Stack ATOM だったらプリンターに、それ以外だったら液晶に、それぞれ出力するサンプルです。
 どちらも LovyanGFX を用いますが、プリンターの場合は 2021/12/31 時点では developブランチ が必要です。
 また、プリンターの場合は更に拙作の LGFX_PrinterAddon も必要です。


 その他 TinyGSMArduinoHttpClient も利用しています。

/*
    https://twitter.com/wakwak_koba/
*/

#define TINY_GSM_MODEM_SIM7080
#define TINY_GSM_RX_BUFFER 1500
#define TINY_GSM_YIELD() { delay(1); }
#include <TinyGsmClient.h>
#include <ArduinoHttpClient.h>

# define apn "povo.jp"
# define server "t.wakwak-koba.jp"
# define port 80
# define url "/garakuta/monodora.png"

TinyGsm        modem(Serial1);
TinyGsmClient  client(modem);

# define LGFX_USE_V1
# define LGFX_AUTODETECT
#include <LovyanGFX.hpp>

# if defined(ARDUINO_M5Stack_ATOM)
  #include <SerialPrinter.hpp>
  #include <printer/aiebcy/EM5820.hpp>
  static lgfx_addon::SerialPrinter<lgfx_addon::aiebcy::EM5820> output(&Serial2, 192, 19);
# else
  static LGFX output;
# endif

void setup() {
  Serial.begin(115200);

# if defined(ARDUINO_M5Stack_ATOM)
  Serial1.begin(9600, SERIAL_8N1, 32, 26);  // CAT-M Unit
  Serial2.begin(9600, SERIAL_8N1, 33, 23);  // ATOM Printer
# else  
  Serial1.begin(9600, SERIAL_8N1, 22, 21);  // CAT-M Unit
# endif
  
  delay(5000);
  Serial.println("restart modem");
  modem.restart();

  Serial.println("wait for network connected ");
//modem.gprsConnect(apn, "", ""); // 初回だけ必要かも
  if (modem.waitForNetwork() && modem.isNetworkConnected()) {
    Serial.print("connecting to ");
    Serial.println(apn);
    modem.gprsConnect(apn, "", "");
    if(modem.isGprsConnected()) {
      Serial.print("connecting to ");
      Serial.println(server);
      auto httpClient = HttpClient(client, server, port);

      Serial.print("get ");
      Serial.println(url);
      httpClient.get(url);     
      auto statusCode = httpClient.responseStatusCode();
      auto contentLength = httpClient.contentLength();
      Serial.printf("StatusCode:%d", statusCode);
      Serial.println();
      Serial.printf("contentLength:%d", contentLength);
      Serial.println();

      output.init();
      output.clear(TFT_WHITE); 

      uint8_t* buf = new uint8_t[contentLength];
      if(buf != nullptr) {
        httpClient.read(buf, contentLength);
        output.drawPng(buf, contentLength);
        delete []buf;
      } else {
        lgfx::StreamWrapper stream;
        stream.set(&httpClient, contentLength);
        output.drawPng(&stream);
      }

      output.setTextColor(TFT_BLACK);
      output.setFont(&fonts::Font4);
      output.setTextDatum(textdatum_t::top_right);
      output.drawString("HELLO", output.width() - 1, 0);
      output.drawString("DORAEMON", output.width() - 1, 30);
# if defined(ARDUINO_M5Stack_ATOM)
      output.display();
# endif    

      httpClient.stop();
      modem.gprsDisconnect();
    }
  }
    
  Serial.println("disconnected");
}

void loop() {
  delay(1);
}