office-addin-sso
First released in December 2019.
This package provides the ability to register an application in Azure Active Directory and for implementing single sign on task pane add-ins.
link - npmjs.com/package/office-addin-sso
link - github.com/OfficeDev/Office-Addin-Scripts/tree/master/packages/office-addin-sso
msgraph-helper.ts
The "/auth" and "/getuserdata" are server-side Express JS routes that exchanges the bootstrap token with Azure AD for an access token with permissions to Microsoft Graph.
import { ODataHelper } from "./odata-helper";
import { showMessage } from "./message-helper";
import * as $ from "jquery";
const domain: string = "graph.microsoft.com";
const versionURLsegment: string = "/v1.0";
export async function getGraphData(
accessToken: string,
apiURLsegment: string,
queryParamsSegment?: string
): Promise<any> {
try {
const oData = await ODataHelper.getData(accessToken, domain, apiURLsegment, versionURLsegment, queryParamsSegment);
return Promise.resolve(oData);
} catch (err) {
return Promise.reject(`Error get Graph data. \n${err}`);
}
}
export async function getGraphToken(bootstrapToken): Promise<any> {
try {
let response = await $.ajax({
type: "GET",
url: "/auth",
headers: { Authorization: "Bearer " + bootstrapToken },
cache: false,
});
return response;
} catch (err) {
throw new Error(`Error getting Graph token. \n${err}`);
}
}
export async function makeGraphApiCall(accessToken: string): Promise<any> {
try {
const response = await $.ajax({
type: "GET",
url: "/getuserdata",
headers: { access_token: accessToken },
cache: false,
});
return response;
} catch (err) {
showMessage(`Error from Microsoft Graph. \n${err}`);
}
}
odata-helper.ts
import * as https from "https";
export class ODataHelper {
static getData(
accessToken: string,
domain: string,
apiURLsegment: string,
apiVersion?: string,
queryParamsSegment?: string
) {
return new Promise<any>((resolve, reject) => {
const options = {
host: domain,
path: apiVersion + apiURLsegment + queryParamsSegment,
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: "Bearer " + accessToken,
"Cache-Control": "private, no-cache, no-store, must-revalidate",
Expires: "-1",
Pragma: "no-cache",
},
};
https
.get(options, (response) => {
let body = "";
response.on("data", (d) => {
body += d;
});
response.on("end", () => {
let error;
if (response.statusCode === 200) {
let parsedBody = JSON.parse(body);
resolve(parsedBody);
} else {
error = new Error();
error.code = response.statusCode;
error.message = response.statusMessage;
body = body.trim();
error.bodyCode = JSON.parse(body).error.code;
error.bodyMessage = JSON.parse(body).error.message;
resolve(error);
}
});
})
.on("error", reject);
});
}
}
message-helper.ts
export function showMessage(text: string): void {
$(".welcome-body").hide();
$("#message-area").show();
$("#message-area").text(text);
}
commands.ts
import * as chalk from "chalk";
import { parseNumber } from "office-addin-cli";
import { ManifestInfo, OfficeAddinManifest } from "office-addin-manifest";
import { usageDataObject } from "./defaults";
import * as configure from "./configure";
import { SSOService } from "./server";
import { addSecretToCredentialStore, writeApplicationData, applicationDataConfigured } from "./ssoDataSettings";
import { ExpectedError } from "office-addin-usage-data";
import inquirer = require("inquirer");
/* global process, console */
export async function configureSSO(manifestPath: string) {
// Check platform and return if not Windows or Mac
if (process.platform !== "win32" && process.platform !== "darwin") {
console.log(chalk.yellow(`${process.platform} is not supported. Only Windows and Mac are supported`));
return;
} else if (applicationDataConfigured(manifestPath)) {
console.log(chalk.yellow("Project was already previously updated."));
const question = {
message: `Continue anyway?`,
name: "didUserConfirm",
type: "confirm",
};
const answer = await inquirer.prompt([question]);
if (!answer.didUserConfirm) {
return;
}
}
const port: number = parseDevServerPort(process.env.npm_package_config_dev_server_port) || 3000;
// Log start time for configuration process
const ssoConfigStartTime = new Date().getTime();
// Check to see if Azure CLI is installed. If it isn't installed, then install it
const cliInstalled = await configure.isAzureCliInstalled();
if (!cliInstalled) {
console.log(
chalk.yellow("Azure CLI is not installed. Installing now before proceeding - this could take a few minutes.")
);
await configure.installAzureCli();
if (process.platform === "win32") {
console.log(
chalk.green(
"Please close your command shell, reopen and run configure-sso again. This is necessary to register the path to the Azure CLI"
)
);
}
return;
}
console.log("Opening browser for authentication to Azure. Enter valid Azure credentials");
const userJson: Object = await configure.logIntoAzure();
if (Object.keys(userJson).length >= 1) {
console.log("Login was successful!");
const manifestInfo: ManifestInfo = await OfficeAddinManifest.readManifestFile(manifestPath);
// Register application in Azure
console.log("Registering new application in Azure");
const applicationJson: Object = await configure.createNewApplication(
manifestInfo.displayName,
port.toString(),
userJson
);
if (applicationJson) {
console.log("Application was successfully registered with Azure");
// Set application IdentifierUri
console.log("Setting identifierUri");
await configure.setIdentifierUri(applicationJson, port.toString());
// Set application sign-in audience
console.log("Setting signin audience");
await configure.setSignInAudience(applicationJson);
// Grant admin consent for application if logged-in user is a tenant admin
if (await configure.isUserTenantAdmin(userJson)) {
console.log("Granting admin consent");
await configure.grantAdminConsent(applicationJson);
// Check to set if SharePoint reply urls are set for tenant. If not, set them
const setSharePointReplyUrls: boolean = await configure.setSharePointTenantReplyUrls(
applicationJson["publisherDomain"].substr(0, applicationJson["publisherDomain"].indexOf("."))
);
if (setSharePointReplyUrls) {
console.log("Set SharePoint reply urls for tenant");
}
// Check to set if Outlook reply url is set for tenant. If not, set them
const setOutlookReplyUrl: boolean = await configure.setOutlookTenantReplyUrl();
if (setOutlookReplyUrl) {
console.log("Set Outlook reply url for tenant");
}
}
// Create an application secret and add to the credential store
console.log("Setting application secret");
const secret: string = await configure.setApplicationSecret(applicationJson);
console.log(chalk.green(`App secret is ${secret}`));
// Add secret to Credential Store (Windows) or Keychain(Mac)
if (process.platform === "win32") {
console.log(`Adding application secret for ${manifestInfo.displayName} to Windows Credential Store`);
} else {
console.log(`Adding application secret for ${manifestInfo.displayName} to Mac OS Keychain.`);
console.log('You will need to provide an admin password to update the Keychain');
}
addSecretToCredentialStore(manifestInfo.displayName, secret);
} else {
const errorMessage = "Failed to register application";
usageDataObject.reportException("createNewApplication", errorMessage);
console.log(chalk.red(errorMessage));
return;
}
// Write application data to project files (manifest.xml, .env, src/taskpane/fallbacktaskpane.ts)
console.log(`Updating source files with application ID and port`);
const projectUpdated = await writeApplicationData(applicationJson["appId"], port.toString(), manifestPath);
if (!projectUpdated) {
console.log(
chalk.yellow(
`Project was already previously updated. You will need to update the CLIENT_ID and PORT settings manually`
)
);
}
// Log out of Azure
console.log("Logging out of Azure now");
await configure.logoutAzure();
console.log(
chalk.green(Application with id ${applicationJson["appId"]} successfully registered in Azure.`)
chalk.green('Go to https://ms.portal.azure.com/#home and search for 'App Registrations' to see your application');
)
// Log end time for configuration process and compute in seconds
const ssoConfigEndTime = new Date().getTime();
const ssoConfigDuration = (ssoConfigEndTime - ssoConfigStartTime) / 1000;
// Send usage data
usageDataObject.reportSuccess("configureSSO", {
configDuration: ssoConfigDuration,
});
} else {
const errorMessage: string = "Login to Azure did not succeed";
usageDataObject.reportException("configureSSO", errorMessage);
throw new Error(errorMessage);
}
}
export async function startSSOService(manifestPath: string) {
try {
// Check platform and return if not Windows or Mac
if (process.platform !== "win32" && process.platform !== "darwin") {
console.log(chalk.yellow(`${process.platform} is not supported. Only Windows and Mac are supported`));
throw new ExpectedError(`${process.platform} is not supported. Only Windows and Mac are supported`);
}
const sso = new SSOService(manifestPath);
sso.startSsoService();
usageDataObject.reportSuccess("startSSOService");
} catch (err) {
usageDataObject.reportException("startSSOService", err);
}
}
function parseDevServerPort(optionValue: any): number | undefined {
const devServerPort = parseNumber(optionValue, "--dev-server-port should specify a number.");
if (devServerPort !== undefined) {
if (!Number.isInteger(devServerPort)) {
throw new ExpectedError("--dev-server-port should be an integer.");
}
if (devServerPort < 0 || devServerPort > 65535) {
throw new ExpectedError("--dev-server-port should be between 0 and 65535.");
}
}
return devServerPort;
}
© 2022 Better Solutions Limited. All Rights Reserved. © 2022 Better Solutions Limited TopPrevNext