|
/** |
|
* Script to manage offboarding and setup email configurations, including user suspension. |
|
* Requires Google Admin SDK and Gmail API to be enabled. |
|
*/ |
|
|
|
// Constants |
|
const OFFBOARDED_GROUP_EMAIL = "[email protected]"; // Group email address |
|
const TEMP_EMAIL = "[email protected]"; |
|
const MANAGER_EMAIL = "[email protected]"; // Manager's email for notifications |
|
const USER_EMAIL = "[email protected]"; // Regular user email address to offboard |
|
const MEMBER_EMAIL = null; // Set to null if MEMBER_EMAIL and MANAGER_EMAIL are the same, or provide a distinct value |
|
const GROUP_NAME = "Employee Offboarded Group"; |
|
const GROUP_DESCRIPTION = "This mailbox stores emails of the offboarded user"; |
|
const SLEEP_TIME = 2000; // Delay to avoid rate limits (2 seconds) |
|
|
|
const CONFIG_VALIDATION = { |
|
EMAIL_REGEX: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, |
|
MIN_SLEEP_TIME: 1000, |
|
MIN_QUOTA_THRESHOLD: 500 |
|
}; |
|
|
|
function main() { |
|
try { |
|
// Determine the email to use as MEMBER_EMAIL |
|
const resolvedMemberEmail = MEMBER_EMAIL || MANAGER_EMAIL; |
|
|
|
// Validate inputs |
|
const emails = [OFFBOARDED_GROUP_EMAIL, TEMP_EMAIL, MANAGER_EMAIL, USER_EMAIL, resolvedMemberEmail]; |
|
if (!emails.every(email => CONFIG_VALIDATION.EMAIL_REGEX.test(email))) { |
|
throw new Error('Invalid email format detected'); |
|
} |
|
|
|
if (SLEEP_TIME < CONFIG_VALIDATION.MIN_SLEEP_TIME) { |
|
throw new Error('Sleep time too low - risk of rate limiting'); |
|
} |
|
|
|
// Execute main steps with error handling |
|
createGroupAndAddMember(OFFBOARDED_GROUP_EMAIL, GROUP_NAME, GROUP_DESCRIPTION, resolvedMemberEmail); |
|
forwardEmailsInChunks(USER_EMAIL, OFFBOARDED_GROUP_EMAIL); |
|
automateAliasSwitching(USER_EMAIL, TEMP_EMAIL, OFFBOARDED_GROUP_EMAIL); |
|
suspendUser(USER_EMAIL); |
|
|
|
sendCompletionEmail(MANAGER_EMAIL, 'Offboarding Complete', |
|
`Successfully offboarded and suspended ${USER_EMAIL}`); |
|
} catch (error) { |
|
Logger.log(`Critical error in offboarding process: ${error.message}`); |
|
sendCompletionEmail(MANAGER_EMAIL, 'Offboarding Failed', |
|
`Offboarding process for ${USER_EMAIL} failed: ${error.message}`); |
|
throw error; |
|
} |
|
} |
|
|
|
/** |
|
* Suspends a user account after offboarding is complete. |
|
* |
|
* @param {string} userEmail - The email of the user to be suspended. |
|
*/ |
|
function suspendUser(userEmail) { |
|
const service = getAdminService(); |
|
if (!service.hasAccess()) { |
|
Logger.log(`Authorization required. Visit: ${service.getAuthorizationUrl()}`); |
|
return; |
|
} |
|
|
|
try { |
|
const url = `https://admin.googleapis.com/admin/directory/v1/users/${userEmail}`; |
|
UrlFetchApp.fetch(url, { |
|
method: 'PATCH', |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
'Content-Type': 'application/json', |
|
}, |
|
payload: JSON.stringify({ |
|
suspended: true |
|
}), |
|
}); |
|
|
|
Logger.log(`User ${userEmail} has been successfully suspended.`); |
|
} catch (error) { |
|
Logger.log(`Failed to suspend user ${userEmail}: ${error.message}`); |
|
throw new Error(`User suspension failed: ${error.message}`); |
|
} |
|
} |
|
|
|
function createGroupAndAddMember(groupEmail, groupName, description, memberEmail) { |
|
const service = getAdminService(); |
|
if (!service.hasAccess()) { |
|
Logger.log(`Authorization required. Visit: ${service.getAuthorizationUrl()}`); |
|
return; |
|
} |
|
|
|
// Create the Group |
|
const groupPayload = { |
|
email: groupEmail, |
|
name: groupName, |
|
description: description, |
|
}; |
|
|
|
const createGroupUrl = 'https://admin.googleapis.com/admin/directory/v1/groups'; |
|
const createGroupResponse = UrlFetchApp.fetch(createGroupUrl, { |
|
method: 'POST', |
|
contentType: 'application/json', |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
}, |
|
payload: JSON.stringify(groupPayload), |
|
}); |
|
|
|
const createdGroup = JSON.parse(createGroupResponse.getContentText()); |
|
Logger.log(`Group created successfully: ${createdGroup.email}`); |
|
|
|
// Add a Member to the Group |
|
const memberPayload = { |
|
email: memberEmail, |
|
role: 'MEMBER', |
|
}; |
|
const addMemberUrl = `https://admin.googleapis.com/admin/directory/v1/groups/${groupEmail}/members`; |
|
const addMemberResponse = UrlFetchApp.fetch(addMemberUrl, { |
|
method: 'POST', |
|
contentType: 'application/json', |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
}, |
|
payload: JSON.stringify(memberPayload), |
|
}); |
|
|
|
const addedMember = JSON.parse(addMemberResponse.getContentText()); |
|
Logger.log(`Member added successfully: ${addedMember.email}`); |
|
} |
|
|
|
function forwardEmailsInChunks(userEmail, groupEmail) { |
|
const service = getOAuthService(userEmail); |
|
const url = `https://gmail.googleapis.com/gmail/v1/users/${userEmail}/messages`; |
|
let nextPageToken = PropertiesService.getScriptProperties().getProperty("NEXT_PAGE_TOKEN"); |
|
let processedCount = parseInt(PropertiesService.getScriptProperties().getProperty("PROCESSED_COUNT") || "0"); |
|
|
|
do { |
|
const paginatedUrl = nextPageToken ? `${url}?pageToken=${nextPageToken}` : url; |
|
const response = UrlFetchApp.fetch(paginatedUrl, { |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
}, |
|
}); |
|
|
|
const result = JSON.parse(response.getContentText()); |
|
const messages = result.messages || []; |
|
|
|
if (messages.length > 0) { |
|
const messageIds = messages.map((msg) => msg.id); |
|
batchForwardEmails(userEmail, groupEmail, messageIds); |
|
processedCount += messageIds.length; |
|
Logger.log(`Processed ${processedCount} emails so far.`); |
|
} |
|
|
|
nextPageToken = result.nextPageToken; |
|
PropertiesService.getScriptProperties().setProperty("NEXT_PAGE_TOKEN", nextPageToken); |
|
PropertiesService.getScriptProperties().setProperty("PROCESSED_COUNT", processedCount); |
|
|
|
if (nextPageToken) Utilities.sleep(SLEEP_TIME); |
|
} while (nextPageToken && ScriptApp.getRemainingDailyQuota() > CONFIG_VALIDATION.MIN_QUOTA_THRESHOLD); |
|
|
|
if (nextPageToken) { |
|
Logger.log("Execution time exceeded. Resuming..."); |
|
setResumeTrigger(); |
|
} else { |
|
Logger.log("Email forwarding complete."); |
|
PropertiesService.getScriptProperties().deleteAllProperties(); |
|
} |
|
} |
|
|
|
function batchForwardEmails(userEmail, groupEmail, messageIds) { |
|
const service = getOAuthService(userEmail); |
|
const batchSendUrl = `https://gmail.googleapis.com/gmail/v1/users/${groupEmail}/messages/send`; |
|
|
|
messageIds.forEach((messageId) => { |
|
try { |
|
const messageUrl = `https://gmail.googleapis.com/gmail/v1/users/${userEmail}/messages/${messageId}`; |
|
const messageResponse = UrlFetchApp.fetch(messageUrl, { |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
}, |
|
}); |
|
|
|
const rawEmail = JSON.parse(messageResponse.getContentText()).raw; |
|
|
|
// Forward the email |
|
UrlFetchApp.fetch(batchSendUrl, { |
|
method: "POST", |
|
contentType: "application/json", |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
}, |
|
payload: JSON.stringify({ |
|
raw: rawEmail |
|
}), |
|
}); |
|
|
|
} catch (error) { |
|
Logger.log(`Error forwarding email ID ${messageId}: ${error.message}`); |
|
} |
|
}); |
|
|
|
Logger.log(`Batch forwarded ${messageIds.length} messages.`); |
|
} |
|
|
|
/** |
|
* Automates the process of switching email aliases for a user. |
|
* |
|
* This function performs the following steps: |
|
* 1. Adds a temporary alias to the current primary email. |
|
* 2. Waits for 2 minutes to ensure the alias is added. |
|
* 3. Changes the primary email to the temporary alias. |
|
* 4. Waits for 2 minutes to ensure the primary email is switched. |
|
* 5. Adds the old email as an alias to the new primary account. |
|
* |
|
* @param {string} oldEmail - The current primary email address of the user. |
|
* @param {string} tempAlias - The temporary alias to be added to the current primary email. |
|
* @param {string} newPrimary - The new primary email address to which the old email will be added as an alias. |
|
* |
|
* @throws Will log an error message if any step in the alias switching process fails. |
|
*/ |
|
function automateAliasSwitching(oldEmail, tempAlias, newPrimary) { |
|
const service = getAdminService(); |
|
if (!service.hasAccess()) { |
|
Logger.log(`Authorization required. Visit: ${service.getAuthorizationUrl()}`); |
|
return; |
|
} |
|
|
|
try { |
|
// Step 1: Add a temporary alias to the current primary email |
|
Logger.log(`Adding alias ${tempAlias} to ${oldEmail}`); |
|
UrlFetchApp.fetch(`https://admin.googleapis.com/admin/directory/v1/users/${oldEmail}/aliases`, { |
|
method: 'POST', |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
'Content-Type': 'application/json', |
|
}, |
|
payload: JSON.stringify({ |
|
alias: tempAlias |
|
}), |
|
}); |
|
|
|
// Wait 2 minutes |
|
Utilities.sleep(120000); |
|
|
|
// Step 2: Change the primary email to the temporary alias |
|
Logger.log(`Switching primary email from ${oldEmail} to ${tempAlias}`); |
|
UrlFetchApp.fetch(`https://admin.googleapis.com/admin/directory/v1/users/${oldEmail}`, { |
|
method: 'PATCH', |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
'Content-Type': 'application/json', |
|
}, |
|
payload: JSON.stringify({ |
|
primaryEmail: tempAlias |
|
}), |
|
}); |
|
|
|
// Wait 2 minutes |
|
Utilities.sleep(120000); |
|
|
|
// Step 3: Add the old email as an alias to the new primary account |
|
Logger.log(`Adding alias ${oldEmail} to ${newPrimary}`); |
|
UrlFetchApp.fetch(`https://admin.googleapis.com/admin/directory/v1/users/${newPrimary}/aliases`, { |
|
method: 'POST', |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
'Content-Type': 'application/json', |
|
}, |
|
payload: JSON.stringify({ |
|
alias: oldEmail |
|
}), |
|
}); |
|
|
|
Logger.log('Alias switching process completed successfully!'); |
|
} catch (error) { |
|
Logger.log(`Error during alias switching: ${error.message}`); |
|
throw new Error(`Failed to switch aliases: ${error.message}`); |
|
} |
|
} |
|
|
|
function setResumeTrigger() { |
|
ScriptApp.newTrigger("forwardEmailsInChunks") |
|
.timeBased() |
|
.after(1000) // Execute after 1 second |
|
.create(); |
|
} |
|
|
|
function getOAuthService(userEmail) { |
|
return OAuth2.createService(`Gmail-${userEmail}`) |
|
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth') |
|
.setTokenUrl('https://accounts.google.com/o/oauth2/token') |
|
.setClientId('YOUR_CLIENT_ID') |
|
.setClientSecret('YOUR_CLIENT_SECRET') |
|
.setCallbackFunction('authCallback') |
|
.setPropertyStore(PropertiesService.getScriptProperties()) |
|
.setScope('https://www.googleapis.com/auth/gmail.modify'); |
|
} |
|
|
|
function getAdminService() { |
|
const key = PropertiesService.getScriptProperties().getProperty('SERVICE_ACCOUNT_KEY'); |
|
if (!key) { |
|
throw new Error('SERVICE_ACCOUNT_KEY is missing in Script Properties.'); |
|
} |
|
|
|
const serviceAccountKey = JSON.parse(Utilities.newBlob(Utilities.base64Decode(key)).getDataAsString()); |
|
return OAuth2.createService('AdminSDK') |
|
.setTokenUrl('https://oauth2.googleapis.com/token') |
|
.setPrivateKey(serviceAccountKey.private_key) |
|
.setClientId(serviceAccountKey.client_id) |
|
.setIssuer(serviceAccountKey.client_email) |
|
.setScope([ |
|
'https://www.googleapis.com/auth/admin.directory.group', |
|
'https://www.googleapis.com/auth/admin.directory.group.member', |
|
'https://www.googleapis.com/auth/admin.directory.user' |
|
].join(' ')) |
|
.setPropertyStore(PropertiesService.getScriptProperties()); |
|
} |
|
|
|
function sendCompletionEmail(recipient, subject, message) { |
|
GmailApp.sendEmail(recipient, subject, message); |
|
} |
|
|
|
function validateGroupExists(groupEmail) { |
|
try { |
|
const service = getAdminService(); |
|
const url = `https://admin.googleapis.com/admin/directory/v1/groups/${groupEmail}`; |
|
const response = UrlFetchApp.fetch(url, { |
|
method: 'GET', |
|
headers: { |
|
Authorization: `Bearer ${service.getAccessToken()}`, |
|
}, |
|
}); |
|
Logger.log(`Group ${groupEmail} exists.`); |
|
return true; |
|
} catch (error) { |
|
Logger.log(`Group ${groupEmail} does not exist: ${error.message}`); |
|
return false; |
|
} |
|
} |
|
|
|
function retryApiCall(apiFunction, retries = 3, delay = 5000) { |
|
for (let attempt = 0; attempt < retries; attempt++) { |
|
try { |
|
return apiFunction(); |
|
} catch (error) { |
|
if (attempt === retries - 1) throw error; |
|
Utilities.sleep(delay * Math.pow(2, attempt)); // Exponential backoff |
|
Logger.log(`Retry attempt ${attempt + 1} after error: ${error.message}`); |
|
} |
|
} |
|
} |