أتمتة مراجعة ملفات Google Drive المفتوحة للعامة
هتطلع من المقال بأوتوميشن بسيط يراجع ملفات Google Drive، يكتشف أي ملف متاح للعامة أو للدومين كله، ويبعت تقرير أسبوعي قبل ما الرابط يفضل مفتوح شهور.
مستوى القارئ: متوسط
المشكلة باختصار
اللي بيحصل فعلاً إن حد من الفريق يشارك ملف أسعار، عرض مبيعات، أو Export من قاعدة البيانات برابط عام علشان يخلص مهمة بسرعة. المشكلة مش في المشاركة نفسها. المشكلة إن الرابط يفضل شغال بعد انتهاء الحاجة منه.
لو عندك فريق من 20 شخص وكل واحد بيشارك 5 ملفات في الأسبوع، عندك تقريبًا 400 مشاركة جديدة شهريًا. مراجعة الرقم ده يدويًا مش واقعية. الأفضل تعمل تقرير أسبوعي يطلع الملفات المفتوحة، المالك، نوع الصلاحية، والرابط.
مثال واضح قبل الشرح
افترض إن عندك ملف اسمه q2-pricing.xlsx. صاحب الملف فتحه كـ anyone with the link أثناء اجتماع مع عميل. بعد أسبوعين، الملف اتنسى. الأوتوميشن هيشوف إن نوع الصلاحية anyone، والدور reader أو writer، ثم يضيفه في تقرير: اسم الملف، المالك، الرابط، ومستوى الخطورة.
بالتفاصيل: Google Drive API يقدر يرجع قائمة الملفات من files.list، ثم يقدر يرجع صلاحيات كل ملف من permissions.list. Apps Script يستخدم UrlFetchApp مع توكن المستخدم، وبعدها MailApp.sendEmail يرسل التقرير. الـ trade-off هنا إنك هتستهلك API calls بعدد الملفات، لكن هتكسب رؤية مستمرة بدل مراجعة عشوائية.
الخطوات العملية
- افتح Google Apps Script جديد من حساب عنده صلاحية رؤية الملفات المطلوبة.
- فعّل Drive API من Google Cloud أو من Advanced Google services لو هتستخدم الخدمة المتقدمة.
- اكتب سكربت يجيب الملفات غير المحذوفة.
- لكل ملف، استدعي
permissions.list. - اعتبر
type=anyoneخطر عالي، وtype=domainخطر متوسط حسب طبيعة شركتك. - ابعث تقرير مختصر للمالك أو لمسؤول الأمان مرة أسبوعيًا.
سكربت قابل للنسخ
الافتراض إن الحساب الذي يشغّل السكربت يقدر يشوف الملفات المستهدفة. لو عندك Google Workspace كبير، الأفضل تشغله بحساب إداري أو تنقله لاحقًا إلى Cloud Function مع Domain-Wide Delegation.
const REPORT_TO = 'security@example.com';
const DRIVE_BASE = 'https://www.googleapis.com/drive/v3';
function weeklyDriveSharingAudit() {
const token = ScriptApp.getOAuthToken();
const files = listDriveFiles(token);
const risky = [];
files.forEach(file => {
const permissions = listPermissions(token, file.id);
permissions.forEach(permission => {
const publicAccess = permission.type === 'anyone';
const domainAccess = permission.type === 'domain';
if (publicAccess || domainAccess) {
risky.push({
name: file.name,
owner: (file.owners || []).map(o => o.emailAddress).join(', '),
role: permission.role,
type: permission.type,
link: file.webViewLink
});
}
});
});
const body = risky.length
? risky.map(r => `${r.type}/${r.role} | ${r.name} | ${r.owner} | ${r.link}`).join('\n')
: 'No public or domain-wide files found this week.';
MailApp.sendEmail({
to: REPORT_TO,
subject: `Drive sharing audit: ${risky.length} risky files`,
body
});
}
function listDriveFiles(token) {
const url = DRIVE_BASE + '/files?' + [
'q=' + encodeURIComponent("trashed=false and mimeType != 'application/vnd.google-apps.folder'"),
'pageSize=100',
'fields=' + encodeURIComponent('files(id,name,owners,emailAddress,webViewLink),nextPageToken')
].join('&');
const res = UrlFetchApp.fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
return JSON.parse(res.getContentText()).files || [];
}
function listPermissions(token, fileId) {
const url = `${DRIVE_BASE}/files/${fileId}/permissions?fields=permissions(id,type,role,emailAddress,domain)`;
const res = UrlFetchApp.fetch(url, {
headers: { Authorization: `Bearer ${token}` },
muteHttpExceptions: true
});
if (res.getResponseCode() >= 300) return [];
return JSON.parse(res.getContentText()).permissions || [];
}