generated from gitea_admin/default
193 lines
5.3 KiB
JavaScript
193 lines
5.3 KiB
JavaScript
import fs from "node:fs"
|
|
import path from "node:path"
|
|
import process from "node:process"
|
|
|
|
const ROOT_DIR = process.cwd()
|
|
|
|
function loadDotEnv(filePath = path.join(ROOT_DIR, ".env")) {
|
|
if (!fs.existsSync(filePath)) {
|
|
return
|
|
}
|
|
|
|
const content = fs.readFileSync(filePath, "utf8")
|
|
|
|
for (const line of content.split(/\r?\n/)) {
|
|
const trimmed = line.trim()
|
|
|
|
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) {
|
|
continue
|
|
}
|
|
|
|
const index = trimmed.indexOf("=")
|
|
const key = trimmed.slice(0, index).trim()
|
|
let value = trimmed.slice(index + 1).trim()
|
|
|
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
value = value.slice(1, -1)
|
|
}
|
|
|
|
if (!process.env[key]) {
|
|
process.env[key] = value
|
|
}
|
|
}
|
|
}
|
|
|
|
function requireEnv(name) {
|
|
const value = process.env[name]
|
|
|
|
if (!value) {
|
|
throw new Error(`Variable d'environnement manquante : ${name}`)
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
async function getAccessToken() {
|
|
const tenantId = requireEnv("MICROSOFT_TENANT_ID")
|
|
const clientId = requireEnv("MICROSOFT_CLIENT_ID")
|
|
const clientSecret = requireEnv("MICROSOFT_CLIENT_SECRET")
|
|
const body = new URLSearchParams({
|
|
client_id: clientId,
|
|
client_secret: clientSecret,
|
|
scope: "https://graph.microsoft.com/.default",
|
|
grant_type: "client_credentials",
|
|
})
|
|
const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body,
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Erreur token Microsoft Graph (${response.status}) : ${await response.text()}`)
|
|
}
|
|
|
|
const data = await response.json()
|
|
return data.access_token
|
|
}
|
|
|
|
function decodeJwtPayload(token) {
|
|
const [, payload] = token.split(".")
|
|
|
|
if (!payload) {
|
|
return {}
|
|
}
|
|
|
|
const normalizedPayload = payload.replaceAll("-", "+").replaceAll("_", "/")
|
|
const paddedPayload = normalizedPayload.padEnd(normalizedPayload.length + ((4 - normalizedPayload.length % 4) % 4), "=")
|
|
|
|
return JSON.parse(Buffer.from(paddedPayload, "base64").toString("utf8"))
|
|
}
|
|
|
|
function parseSharePointSiteUrl(rawUrl) {
|
|
const url = new URL(rawUrl)
|
|
const normalizedPath = url.pathname.replace(/\/+$/, "")
|
|
|
|
if (!normalizedPath) {
|
|
throw new Error("L'URL SharePoint doit contenir le chemin du site, par exemple https://tenant.sharepoint.com/sites/nom-du-site")
|
|
}
|
|
|
|
return {
|
|
hostname: url.hostname,
|
|
sitePath: normalizedPath,
|
|
}
|
|
}
|
|
|
|
async function graphGet(token, url) {
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const details = await response.text()
|
|
const hint = response.status === 401
|
|
? "\nVérifie que l'application Entra ID a des permissions Microsoft Graph de type Application, par exemple Sites.Read.All ou Sites.ReadWrite.All, avec Grant admin consent. Vérifie aussi que le tenant correspond bien au SharePoint."
|
|
: ""
|
|
const error = new Error(`Erreur Microsoft Graph (${response.status}) : ${details}${hint}`)
|
|
error.status = response.status
|
|
error.details = details
|
|
|
|
throw error
|
|
}
|
|
|
|
return response.json()
|
|
}
|
|
|
|
async function main() {
|
|
loadDotEnv()
|
|
|
|
const sharePointUrl = process.argv[2] || process.env.SHAREPOINT_SITE_URL
|
|
|
|
if (!sharePointUrl) {
|
|
throw new Error("Usage : npm run graph:find-drive -- https://tenant.sharepoint.com/sites/nom-du-site")
|
|
}
|
|
|
|
const token = await getAccessToken()
|
|
const claims = decodeJwtPayload(token)
|
|
|
|
console.log(`Token Microsoft Graph obtenu pour tenant=${claims.tid || "inconnu"} app=${claims.appid || claims.azp || "inconnue"}`)
|
|
console.log(`Permissions applicatives dans le token : ${(claims.roles || []).join(", ") || "aucune"}`)
|
|
console.log("")
|
|
|
|
const { hostname, sitePath } = parseSharePointSiteUrl(sharePointUrl)
|
|
let site
|
|
|
|
try {
|
|
site = await graphGet(
|
|
token,
|
|
`https://graph.microsoft.com/v1.0/sites/${hostname}:${sitePath}`
|
|
)
|
|
} catch (error) {
|
|
if (error.status !== 404) {
|
|
throw error
|
|
}
|
|
|
|
const searchTerm = sitePath.split("/").filter(Boolean).at(-1)
|
|
console.log(`Site introuvable avec le chemin ${sitePath}. Recherche des sites contenant "${searchTerm}"...`)
|
|
console.log("")
|
|
|
|
const searchResults = await graphGet(
|
|
token,
|
|
`https://graph.microsoft.com/v1.0/sites?search=${encodeURIComponent(searchTerm)}`
|
|
)
|
|
|
|
if (!searchResults.value?.length) {
|
|
throw error
|
|
}
|
|
|
|
console.log("Sites trouvés :")
|
|
|
|
for (const result of searchResults.value) {
|
|
console.log(`- ${result.displayName || result.name}`)
|
|
console.log(` SITE_ID=${result.id}`)
|
|
console.log(` webUrl=${result.webUrl}`)
|
|
}
|
|
|
|
console.log("")
|
|
console.log("Relance ensuite la commande avec l'URL webUrl exacte du bon site.")
|
|
return
|
|
}
|
|
|
|
const drives = await graphGet(
|
|
token,
|
|
`https://graph.microsoft.com/v1.0/sites/${encodeURIComponent(site.id)}/drives`
|
|
)
|
|
|
|
console.log(`SITE_ID=${site.id}`)
|
|
console.log("")
|
|
console.log("Bibliothèques SharePoint trouvées :")
|
|
|
|
for (const drive of drives.value || []) {
|
|
console.log(`- ${drive.name}`)
|
|
console.log(` GRAPH_DRIVE_ID=${drive.id}`)
|
|
console.log(` webUrl=${drive.webUrl}`)
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error)
|
|
process.exit(1)
|
|
})
|