2024年4月27日 星期六

利用 Curl.exe 提交 Box 的 JWT 請求獲取 Access Token

利用 Curl.exe 提交 Box 的 JWT 請求獲取 Access Token

這邊比較難搞的是JWT,比較無腦一點就是直接載人家函式來弄,編碼之後會變成一長串文字,由Post送出去呼叫伺服器的API。

下面是用 PowerShell 寫的 Curl 請求代碼。這邊要注意一個坑是如果沒有加上 .exe 的話預設會呼叫內建的指令,內建會直接回傳 PowerShell 物件而不是文字,然後參數用法有不同不能直接換過去。


請求 Access Token 的指令

function RequestBoxToken {
    param (
        [string]$Assertion,
        [string]$ConfigPath
    )
    # Read configuration from JSON file
    $configContent = Get-Content $ConfigPath -Raw
    $config = ConvertFrom-Json $configContent

    # Generate Request queryString
    $url = 'https://api.box.com/oauth2/token'
    $proxyUrl = $env:http_proxy
    $response = curl.exe `
        -x $proxyUrl `
        -X POST $url `
        -d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' `
        -d "assertion=$Assertion" `
        -d "client_id=$($config.boxAppSettings.clientID)" `
        -d "client_secret=$($config.boxAppSettings.clientSecret)"
    # RequestBoxAccessToken
    $response = ConvertFrom-Json $response
    return $response
}


$assertion 是 JWT 簽名後的字串,要快速看內容有網站可以看
JSON Web Tokens - jwt.io

$config 是直接從BOX上抓下來的Json檔案,他格式大概會長這樣子


{
  "boxAppSettings": {
    "clientID": "clientID",
    "clientSecret": "clientSecret",
    "appAuth": {
      "publicKeyID": "publicKeyID",
      "privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nSTRING\n-----END ENCRYPTED PRIVATE KEY-----",
      "passphrase": "secret_passphrase"
    }
  },
  "enterpriseID": "enterpriseID"
}


裡面包含私鑰會需要用這個私鑰簽名,才能組合出JWT。

然後其實$config有點多餘的,因為JWT裡面是有包含client_id跟url的,唯一有需要的是client_secret這個沒有被包進去,只是要把JWT解包有點麻煩,懶人法就直接再傳一次了。



Python對應的方法

import requests
import os
import json

def request_box_token(assertion, config_path):
    # 讀取設定檔
    with open(config_path, 'r') as file:
        config = json.load(file)

    url = 'https://api.box.com/oauth2/token'

    # 設置代理
    http_proxy = os.getenv('http_proxy')
    https_proxy = os.getenv('https_proxy')
    proxies = {}
    if http_proxy:
        proxies['http'] = http_proxy
    if https_proxy:
        proxies['https'] = https_proxy

    payload = {
        'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion': assertion,
        'client_id': config['boxAppSettings']['clientID'],
        'client_secret': config['boxAppSettings']['clientSecret']
    }
    response = requests.post(url, data=payload, proxies=proxies if proxies else None)
    return response.json()

# 使用方法
assertion = '你的assertion'
config_path = 'config.json'  # 設定檔路徑
response = request_box_token(assertion, config_path)
print(response)








PowerShell 如何生成時間戳記 相對於 1970-01-01 的秒數

PowerShell 如何生成時間戳記 相對於 1970-01-01 的秒數

在做JWT呼叫API相關的事情,被這時間戳記搞了一會

先說Python上的時間戳記,這東西不用太管直接出來就是正確的

import time
round(time.time())

但是如果要手動實現這個功能就會牽涉到相對時間跟時區的問題了

這裡關鍵就是 Get-Date '1970-01-01 00:00:00' 已經是絕對時間了就不能在加入時差運算了,原本兩邊都加了算出來奇怪怎麼對不上

# 獲取 PowerShell 的時間戳
$TimeStamp1 = [int][Math]::Round(((Get-Date).ToUniversalTime() - (Get-Date '1970-01-01 00:00:00')).TotalSeconds)
Write-Host "PowerShell: $TimeStamp1"

# 獲取 Python 的時間戳
$TimeStamp2 = & python -c "import time; print(round(time.time()))"
Write-Host "Python    : $TimeStamp2"

# 時差
($TimeStamp1-$TimeStamp2)/60/60

-

2024年4月19日 星期五

pnp.powershell 基本用法

pnp.powershell 基本用法

經常要測試,每次都從頭來有點煩了把常見的語法整合起來



安裝

安裝有分好幾個版本,比較要注意的是從2版開始必須要 pwsh7 才能動,環境不允許的話只能裝舊版的。

各版本可以從這裡點出來
powershell/README.md at dev · pnp/powershell (github.com)


夜間最新版

Install-Module -Name PnP.PowerShell -AllowPrerelease

最新穩定版 (PowerShell Gallery | PnP.PowerShell 2.4.0)

Install-Module -Name PnP.PowerShell -RequiredVersion 2.4.0

舊版

Install-Module PnP.PowerShell -RequiredVersion 1.12.0 -Force


基於版本問題,建議在代碼中把這段放進去開頭,可以少走不少彎路

function GetDateString { $((Get-Date).Tostring("yyyy/MM/dd HH:mm:ss.fff")) }
$Module = (Get-Module -ListAvailable PnP.PowerShell)[0]
if (!$Module) { Write-Error "The module 'PnP.PowerShell' is not installed." -EA 1 }
Write-Host "[$(GetDateString)] " -ForegroundColor DarkGray -NoNewline
Write-Host "PowerShellVersion $($PSVersionTable.PSVersion), PnP.PowerShellVersion $($Module.Version)" -ForegroundColor DarkGray

注意看 Get-Module 有用 [0] 取第一個,如果同時存在多個版本的話



登入

登入有好幾種方法,先介紹文字介面的

$SiteUrl = "https://chg.sharepoint.com/sites/Maple"
$User    = "UserName@chg.onmicrosoft.com"
$PWord   = "PassWord"
$PWord = $PWord | ConvertTo-SecureString -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
Connect-PnPOnline -Url $SiteUrl -Credentials $Credential


這樣就登入了,再來可以用這個測試現在登入哪個網站

# 目前登入的用戶
(Get-PnPConnection).PSCredential

# 目前登入的網站
Get-PnPWeb


對於如何合理的儲存資料,可以參考這個專案https://github.com/hunandy14/ConvertFrom-Env 



上傳檔案

這邊要注意一點是斜線的方向,一開始不知道弄了好一會兒。


先用預設的資料夾測試,這個是創建SharePoint時預設會建立的文件庫。

# 獲取預設資料夾
Get-PnPFolder -Url "/Shared Documents"

# 獲取預設資料夾中的子資料夾
Get-PnPFolder -Url "/Shared Documents/File"


創建資料夾

try { # 檢查資料夾是否存在
    Get-PnPFolder -Url "/Shared Documents/File" -ErrorAction Stop
} catch { # 如果資料夾不存在,則建立它
    Add-PnPFolder -Name "File" -Folder "Shared Documents"
}


上傳文件

Add-PnPFile -Path ".\README.md" -Folder "Shared Documents/File"


查看文件

Get-PnPFile -Url "Shared Documents/File/README.md"