2024年11月23日 星期六

跟微軟黑洞入口 outlook 保持距離 (上篇): oauth2

學校通知 zimbra mail server 要退役了; 寒假起要改用微軟 outlook。 一如 以往, 微軟的服務暗藏高昂的 下賊船的代價, 未來如果郵件都留在微軟的伺服器裡, 想要匯出的時候, 必須使用桌面版 outlook, 而且匯出的是微軟的專屬格式, 到時候你就會逐步地被吸入微軟黑洞。 我必須趕快學會定時用 IMAP 把所有的郵件抓回自己的伺服器上。 找到兩篇很棒的文章: Neomutt + isync / mbsync for Office365Local email from Office365 using OAUTH2 with mbsync。 這幾天先做一半: 學會用命令列進行 oauth2 授權。

本文前半 「取得 token」 的動作, 建議在自己手邊的桌機或筆電實作, 不要在遠端伺服器上實作。 (下面解釋) 請先下載 這個 gist 的四個檔案: imap7to8.py、 imapheader.py、 mutt_oauth2.py、 mutt_oauth2.md。 其中 mutt_oauth2.* 來自 debian 13 的 neomutt 套件; 如果你的 debian 夠新, 可以直接安裝最新版 neomutt 套件; debian 12 或更舊的 neomutt 裡面的 mutt_oauth2.py 還不完整。 (mutt_oauth2.md 其實是 /usr/share/doc/neomutt/oauth2/README.md.gz 的解壓縮檔) 請把 mutt_oauth2.py 改成可執行, 並放到 /usr/bin 底下。 它的 README 文件 (mutt_oauth2.md) 用白話解釋為什麼要有 oauth2, 大推! 不過如果你趕時間, 也可以改讀這篇中文: 「從Google第三方登入看OAuth2.0」 只讀到角色介紹為止即可。 現在拿本文的角色套上去:

  • 資料所有者 resource owner: 我 (在學校的 outlook mail 帳號)
  • client: 理論上是 curl 指令。 等一下通過 oauth2 認證之後, 我們要用 curl 指令來登入自己的 outlook 帳號、 查看收件匣中的信件標題。 但實際上... 下詳。
  • 授權中心 authorization server: login.microsoftonline.com 。
  • 資料中心 resource server: 朝陽科大圖書資訊處的某伺服器。

Oauth2 有很多種 授權流程 (flow) (我該選哪一種?) 而 mutt_oauth2.py 提供三種實作: authcode、 localhostauthcode、 devicecode。 前兩者都屬於 authorization code flow 的實作。 目前我只成功採用第二種 "localhostauthcode"; 後續若其他實驗成功, 會補在這裡。

微軟的設計多出兩個步驟。 首先, 看起來微軟並沒有打算讓一般人手動操作; 它認為使用 oauth2 的一定是開發者 (程式設計師), 而且 註冊自己的應用程式 (client 角色) 的步驟 有點囉嗦。 可是我沒有要寫程式啊! 我只想下 curl 指令測試, 有必要那麼官僚嗎? 還好, 感謝有 thunderbird 提供 公開的 client_id 跟 client_secret, 所以我們就省略這一步, 等一下直接借 thunderbird 的名號來進行 oauth2 認證。

另外, 我必須取得 「Microsoft Entra 租用戶識別碼」, 我把它想成是微軟指定給朝陽科大的統一編號。 (對, 在微軟王國裡面經營學校的意思。) 請見 如何尋找您的 Microsoft Entra租用戶識別碼 這篇裡面的 「透過 Azure 入口網站尋找租用戶識別碼」, 透過 Azure 入口網站 登入學校或公司給你的 email 帳號、 進入 「管理 Microsoft Entra ID」, 在學校或公司名稱下面即可看到一個 32 位的十六進位數字, 就是等一下要填的 ORG_TENANT_ID。

接下來終於可以 開動 開始下指令了。

MY_365_MAIL=貴哥@o365.朝陽科大 # 改成你自己的 o365 mail
TB_ID=08162f7c-0fd2-4200-a84a-f25a4db0b584 # 借用 thunderbird 的
TB_SECRET='TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82' # 借用 thunderbird 的
ORG_TENANT_ID=ffffffff-ffff-ffff-ffff-ffffffffffff # 改成你學校/公司的

進行認證: rm -f token.json ; mutt_oauth2.py token.json --verbose --authorize --authflow localhostauthcode --provider microsoft --email $MY_365_MAIL --encryption-pipe cat --decryption-pipe cat --client-id=$TB_ID --client-secret=$TB_SECRET

這時 mutt_oauth2.py 會在 localhost 的某個 port 啟動一個臨時的服務, 並且叫你把一串 (微軟的) 網址貼到瀏覽器。 你在瀏覽器裡登入學校/公司的 outlook 帳號之後, 微軟會根據先前網址後面的參數把 token 丟給臨時啟動的服務, 而 mutt_oauth2.py 也就順利把結果存到 token.json。 [如果當初你在遠端伺服器下 mutt_oauth2.py 指令, 就必須把那串網址貼到遠端伺服器的瀏覽器上, 它才看得到 mutt_oauth2.py 臨時啟動的服務。 也就是說, 你必須用 vnc 或 ssh -X 裡面的瀏覽器來完成這個動作。] 可以用 jq . token.json 查看內容, 應該包含 refresh_token、 access_token、 expires_in、 scope 等等欄位。 也可以這樣測試: ./mutt_oauth2.py -t --decryption-pipe cat token.json (會印出一堆亂碼, 看起來像是一個 token) 也可以把 token 的內容貼到 這裡, 查看 token 的相關資訊。 照理說把 token 貼到隨便的網站很危險; 不過這是微軟自己的網站, 他們不會砸自己的招牌。 (反正你的 mail 都用它的軟體在處理了, 就算裸奔給微軟看, 也沒什麼好在意的。)

補充說明: (1) 為了簡化測試流程, 上面的指令指定不要加密; 如果你在遠端伺服器上下 mutt_oauth2.py 指令, 如果你重視資訊安全, 應該要學會使用 gnupg, 並且省略上面 --encryption-pipe cat --decryption-pipe cat 這兩段, 讓 mutt_oauth2.py 採預設值, 自動呼叫 gpg 加解密。 (2) 最開始的 rm -f token.json 是為了避免既有的 token.json 檔影響到 mutt_oauth2.py 的運作, 因為放在 mutt_oauth2.py 命令列上的那個檔案如果已經存在, mutt_oauth2.py 會採用裡面的資訊。 (3) 拿到的 token, 可以搬到 (copy 到) 別處用, 所以接下來的動作也可以在伺服器上執行。

接下來把 access_token 抓出來: OUTLOOK_TOKEN=$(jq -r .access_token token.json), 然後就可以:

  1. 查詢自己的 o365 帳號內有哪些資料匣: curl > folders.txt --url imaps://outlook.office365.com:993 --user $MY_365_MAIL --oauth2-bearer $OUTLOOK_TOKEN 產生的文字檔 folders.txt 的內容看來像是一堆亂碼, 因為他採用 imap 的特殊 utf7 編碼, 所以要用 imap7to8.py 轉成 utf8 編碼: ./imap7to8.py < folders.txt (在 debian 底下, 需要先 apt install python3-imapclient)
  2. 列出收件匣裡面所有信件的代號, 放在 indexes.txt 裡: curl > indexes.txt --url imaps://outlook.office365.com:993/INBOX --user $MY_365_MAIL --oauth2-bearer $OUTLOOK_TOKEN -X "SEARCH ALL"
  3. 列出收件夾裡面第 1 到 3 封信件的主旨, 放在 subject-list.txt 裡: curl > subject-list.txt --url "imaps://outlook.office365.com:993/INBOX;mailindex=[1-3];section=header.fields%20(subject)" --user $MY_365_MAIL --oauth2-bearer $OUTLOOK_TOKEN 語法是從 這裡 學來的。 這一次則要用 imapheader.py 轉主旨的編碼: ./imapheader.py < subject-list.txt

每個一兩個小時 access_token 就會作廢。 這時需要用 refresh_token 取得新的 access_token: curl > new_token.json -X POST https://login.microsoftonline.com/$ORG_TENANT_ID/oauth2/v2.0/token -H 'Content-Type: application/x-www-form-urlencoded' -d client_id=$TB_ID -d client_secret=$TB_SECRET -d 'grant_type=refresh_token' -d refresh_token=$(jq -r .refresh_token token.json) 如果成功的話, 就取代原先的: cp new_token.json token.json; OUTLOOK_TOKEN=$(jq -r .access_token token.json)

對了, 忘記是哪一步, 微軟的網站拒絕 firefox 瀏覽, 必須改用 chromium。

(下篇待續)

沒有留言:

張貼留言

因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。