Skip to content

Commit

Permalink
graph client intergration (#769)
Browse files Browse the repository at this point in the history
* graph client authentication and service calls

* Microsoft Graph Client Service Added

* rolling back deleted space

* scope constants added in api call

* telemetry error log changed to hard coded string

---------

Co-authored-by: tyaginidhi <[email protected]>
  • Loading branch information
ritikramuka and tyaginidhi authored Nov 21, 2023
1 parent 1d40111 commit 9a0c7e7
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/web/client/WebExtensionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class WebExtensionContext implements IWebExtensionContext {
private _npsEligibility: boolean;
private _userId: string;
private _formsProEligibilityId: string;
private _concurrencyHandler: ConcurrencyHandler
private _concurrencyHandler: ConcurrencyHandler;
// Co-Presence for Power Pages Vscode for web
private _worker: Worker | undefined;
private _sharedWorkSpaceMap: Map<string, string>;
Expand Down
61 changes: 61 additions & 0 deletions src/web/client/common/authenticationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { telemetryEventNames } from "../telemetry/constants";
import {
INTELLIGENCE_SCOPE_DEFAULT,
PROVIDER_ID,
SCOPE_OPTION_CONTACTS_READ,
SCOPE_OPTION_DEFAULT,
SCOPE_OPTION_OFFLINE_ACCESS,
SCOPE_OPTION_USERS_READ_BASIC_ALL,
} from "./constants";
import { ERRORS, showErrorDialog } from "./errorHandler";
import { ITelemetry } from "../../../client/telemetry/ITelemetry";
Expand Down Expand Up @@ -161,3 +163,62 @@ export async function npsAuthentication(

return accessToken;
}

export async function graphClientAuthentication(
firstTimeAuth = false
): Promise<string> {
let accessToken = "";
try {
let session = await vscode.authentication.getSession(
PROVIDER_ID,
[
SCOPE_OPTION_CONTACTS_READ,
SCOPE_OPTION_USERS_READ_BASIC_ALL,
],
{ silent: true }
);

if (!session) {
session = await vscode.authentication.getSession(
PROVIDER_ID,
[
SCOPE_OPTION_CONTACTS_READ,
SCOPE_OPTION_USERS_READ_BASIC_ALL,
],
{ createIfNone: true }
);
}

accessToken = session?.accessToken ?? "";
if (!accessToken) {
throw new Error(ERRORS.NO_ACCESS_TOKEN);
}

if (firstTimeAuth) {
WebExtensionContext.telemetry.sendInfoTelemetry(
telemetryEventNames.WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED,
{
userId:
session?.account.id.split("/").pop() ??
session?.account.id ??
"",
}
);
}
} catch (error) {
const authError = (error as Error)?.message;
showErrorDialog(
vscode.l10n.t(
"Authorization Failed. Please run again to authorize it"
),
vscode.l10n.t("There was a permissions problem with the server")
);
WebExtensionContext.telemetry.sendErrorTelemetry(
telemetryEventNames.WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED,
graphClientAuthentication.name,
authError
);
}

return accessToken;
}
24 changes: 19 additions & 5 deletions src/web/client/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ export const MAX_CONCURRENT_REQUEST_QUEUE_COUNT = 1000;
export const INTELLIGENCE_SCOPE_DEFAULT = "https://text.pai.dynamics.com/.default";
export const BACK_TO_STUDIO_URL_TEMPLATE = "https://make{.region}.powerpages.microsoft.com/e/{environmentId}/sites/{webSiteId}/pages";
export const STUDIO_PROD_REGION = "prod";
export const ARTEMIS_RESPONSE_FAILED = "Artemis response failed"
export const WEB_EXTENSION_FETCH_WORKER_SCRIPT_FAILED = "Web extension fetch worker script failed"
export const WEB_EXTENSION_POPULATE_SHARED_WORKSPACE_SYSTEM_ERROR = "Web extension populate shared workspace system error"
export const WEB_EXTENSION_WEB_WORKER_REGISTRATION_FAILED = "Web extension web worker registration failed"
export const WEB_EXTENSION_FETCH_GET_OR_CREATE_SHARED_WORK_SPACE_ERROR = "Web extension fetch get or create shared workspace error"
export const ARTEMIS_RESPONSE_FAILED = "Artemis response failed";
export const WEB_EXTENSION_GET_FROM_GRAPH_CLIENT_FAILED = "Web extension get from graph client failed";
export const WEB_EXTENSION_FETCH_WORKER_SCRIPT_FAILED = "Web extension fetch worker script failed";
export const WEB_EXTENSION_POPULATE_SHARED_WORKSPACE_SYSTEM_ERROR = "Web extension populate shared workspace system error";
export const WEB_EXTENSION_WEB_WORKER_REGISTRATION_FAILED = "Web extension web worker registration failed";
export const WEB_EXTENSION_FETCH_GET_OR_CREATE_SHARED_WORK_SPACE_ERROR = "Web extension fetch get or create shared workspace error";

// Web extension constants
export const BASE_64 = 'base64';
Expand Down Expand Up @@ -117,3 +118,16 @@ export enum portalSchemaVersion {
V1 = "portalschemav1",
V2 = "portalschemav2",
}

// Microsoft Graph Client constants
export const SCOPE_OPTION_CONTACTS_READ = "Contacts.Read";
export const SCOPE_OPTION_USERS_READ_BASIC_ALL = "User.ReadBasic.All";

export const MICROSOFT_GRAPH_USERS_BASE_URL = "https://graph.microsoft.com/v1.0/users/";

export enum GraphService {
GRAPH_MAIL = "mail",
GRAPH_PROFILE_PICTURE = "profilePicture",
}

export const MICROSOFT_GRAPH_PROFILE_PICTURE_SERVICE_CALL = "/photo/$value";
152 changes: 152 additions & 0 deletions src/web/client/services/graphClientService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import path from "path";
import WebExtensionContext from "../WebExtensionContext";
import { getCommonHeaders, graphClientAuthentication } from "../common/authenticationProvider";
import * as Constants from "../common/constants";
import { telemetryEventNames } from "../telemetry/constants";

export class GraphClientService {
private _graphToken: string;

constructor() {
this._graphToken = "";
}

get graphToken() {
return this._graphToken;
}

public async graphClientAuthentication(firstTimeAuth = false) {
const accessToken = await graphClientAuthentication(firstTimeAuth);
this._graphToken = accessToken;
}

private async requestGraphClient(
service: string,
userId: string
) {
let requestSentAtTime = new Date().getTime();

const basePath = Constants.MICROSOFT_GRAPH_USERS_BASE_URL;
let requestUrl;

switch (service) {
case Constants.GraphService.GRAPH_MAIL:
requestUrl = new URL(userId, basePath);
break;
case Constants.GraphService.GRAPH_PROFILE_PICTURE:
requestUrl = new URL(
path.join(
userId,
Constants.MICROSOFT_GRAPH_PROFILE_PICTURE_SERVICE_CALL
),
basePath
);
break;
default:
return;
}

try {
WebExtensionContext.telemetry.sendAPITelemetry(
requestUrl.href,
service,
Constants.httpMethod.GET,
this.requestGraphClient.name
);

requestSentAtTime = new Date().getTime();

const response =
await WebExtensionContext.concurrencyHandler.handleRequest(
requestUrl.href,
{
headers: {
...getCommonHeaders(this._graphToken),
},
}
);

if (!response.ok) {
throw new Error(JSON.stringify(response));
}

WebExtensionContext.telemetry.sendAPISuccessTelemetry(
requestUrl.href,
service,
Constants.httpMethod.POST,
new Date().getTime() - requestSentAtTime,
this.requestGraphClient.name
);

return await response.json();
} catch (error) {
const errorMsg = (error as Error)?.message;
if ((error as Response)?.status > 0) {
WebExtensionContext.telemetry.sendAPIFailureTelemetry(
requestUrl.href,
service,
Constants.httpMethod.GET,
new Date().getTime() - requestSentAtTime,
this.requestGraphClient.name,
errorMsg,
"",
(error as Response)?.status.toString()
);
} else {
WebExtensionContext.telemetry.sendErrorTelemetry(
telemetryEventNames.WEB_EXTENSION_GET_FROM_GRAPH_CLIENT_FAILED,
this.requestGraphClient.name,
Constants.WEB_EXTENSION_GET_FROM_GRAPH_CLIENT_FAILED,
error as Error
);
}
}
}

public async getUserEmail(userId: string) {
try {
if (!this._graphToken) {
await this.graphClientAuthentication(true);
}

const response = await this.requestGraphClient(
Constants.GraphService.GRAPH_MAIL,
userId
);
return await response.mail;
} catch (error) {
WebExtensionContext.telemetry.sendErrorTelemetry(
telemetryEventNames.WEB_EXTENSION_GET_EMAIL_FROM_GRAPH_CLIENT_FAILED,
this.getUserEmail.name,
(error as Error)?.message,
error as Error
);
}
}

public async getUserProfilePicture(userId: string) {
try {
if (!this._graphToken) {
await this.graphClientAuthentication(true);
}

const response = await this.requestGraphClient(
Constants.GraphService.GRAPH_PROFILE_PICTURE,
userId
);
return await response;
} catch (error) {
WebExtensionContext.telemetry.sendErrorTelemetry(
telemetryEventNames.WEB_EXTENSION_GET_PROFILE_PICTURE_FROM_GRAPH_CLIENT_FAILED,
this.getUserProfilePicture.name,
(error as Error)?.message,
error as Error
);
}
}
}
5 changes: 5 additions & 0 deletions src/web/client/telemetry/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,9 @@ export enum telemetryEventNames {
WEB_EXTENSION_ARTEMIS_RESPONSE = 'webExtensionArtemisResponse',
WEB_EXTENSION_ARTEMIS_RESPONSE_FAILED = 'webExtensionArtemisResponseFailed',
WEB_EXTENSION_FAILED_TO_UPDATE_FOREIGN_KEY_DETAILS = 'webExtensionFailedToUpdateForeignKeyDetails',
WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED = "WebExtensionGraphClientAuthenticationFailed",
WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED = "WebExtensionGraphClientAuthenticationCompleted",
WEB_EXTENSION_GET_FROM_GRAPH_CLIENT_FAILED = "WebExtensionGetFromGraphClientFailed",
WEB_EXTENSION_GET_EMAIL_FROM_GRAPH_CLIENT_FAILED = "WebExtensionGetEmailFromGraphClientFailed",
WEB_EXTENSION_GET_PROFILE_PICTURE_FROM_GRAPH_CLIENT_FAILED = "WebExtensionGetProfilePictureFromGraphClientFailed",
}

0 comments on commit 9a0c7e7

Please sign in to comment.