Skip to content

Commit

Permalink
WIP: myriad type changes that are affecting data serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
musale committed Dec 2, 2024
1 parent a6eb6d6 commit cd85eca
Show file tree
Hide file tree
Showing 23 changed files with 388 additions and 355 deletions.
4 changes: 2 additions & 2 deletions src/app/services/actions/autocomplete-action-creators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ const mockState: ApplicationState = {
graphResponse: {
isLoadingData: false,
response: {
body: undefined,
headers: undefined
body: '',
headers: {}
}
},
snippets: {
Expand Down
4 changes: 2 additions & 2 deletions src/app/services/actions/permissions-action-creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ const mockState: ApplicationState = {
graphResponse: {
isLoadingData: false,
response: {
body: undefined,
headers: undefined
body: '',
headers: {}
}
},
snippets: {
Expand Down
43 changes: 20 additions & 23 deletions src/app/services/actions/permissions-action-creator.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IQuery } from '../../../types/query-runner';
import { RevokeScopesError } from '../../utils/error-utils/RevokeScopesError';
import { exponentialFetchRetry } from '../../utils/fetch-retry-handler';
import { GRAPH_URL } from '../graph-constants';
import { makeGraphRequest, parseResponse } from './query-action-creator-util';
import { makeGraphRequest as permsmakeGraphRequest } from './query-action-creator-util';

interface IPreliminaryChecksObject {
defaultUserScopes: string[];
Expand Down Expand Up @@ -106,8 +106,9 @@ export class RevokePermissionsUtil {
public static async isSignedInUserTenantAdmin(): Promise<boolean> {
const tenantAdminQuery = { ...genericQuery };
tenantAdminQuery.sampleUrl = `${GRAPH_URL}/v1.0/me/memberOf`;
const response = await RevokePermissionsUtil.makeExponentialFetch([], tenantAdminQuery);
return response ? response.value.some((value: any) => value.displayName === 'Global Administrator') : false
const response = await RevokePermissionsUtil.makeExponentialFetch(
[], tenantAdminQuery) as { value: { displayName: string }[] };
return response ? response.value.some((value) => value.displayName === 'Global Administrator') : false
}

public async getUserPermissionChecks(preliminaryObject: PartialCheckObject): Promise<{
Expand Down Expand Up @@ -172,7 +173,7 @@ export class RevokePermissionsUtil {
}
if (!grantsPayload) { return emptyGrant }

return grantsPayload.value.find((grant: any) =>
return grantsPayload.value.find((grant: IPermissionGrant) =>
grant.consentType.toLowerCase() === 'AllPrincipals'.toLowerCase()) || emptyGrant;
}

Expand All @@ -190,7 +191,7 @@ export class RevokePermissionsUtil {
if (!servicePrincipalAppId) { return { value: [], '@odata.context': '' } }
genericQuery.sampleUrl = `${GRAPH_URL}/v1.0/oauth2PermissionGrants?$filter=clientId eq '${servicePrincipalAppId}'`;
genericQuery.sampleHeaders = [{ name: 'ConsistencyLevel', value: 'eventual' }];
const oAuthGrant = await RevokePermissionsUtil.makeExponentialFetch(scopes, genericQuery);
const oAuthGrant = await RevokePermissionsUtil.makeExponentialFetch(scopes, genericQuery) as IOAuthGrantPayload;
return oAuthGrant;
}

Expand All @@ -206,8 +207,8 @@ export class RevokePermissionsUtil {
public static async getServicePrincipalId(scopes: string[]): Promise<string> {
const currentAppId = process.env.REACT_APP_CLIENT_ID;
genericQuery.sampleUrl = `${GRAPH_URL}/v1.0/servicePrincipals?$filter=appId eq '${currentAppId}'`;
const response = await this.makeGraphRequest(scopes, genericQuery);
return response ? response.value[0].id : '';
const response = await this.makeGraphRequest(scopes, genericQuery) as { value: { id: string }[] };
return response && response.value.length > 0 ? response.value[0].id : '';
}

private async revokePermission(permissionGrantId: string, newScopes: string): Promise<boolean> {
Expand All @@ -219,35 +220,31 @@ export class RevokePermissionsUtil {
patchQuery.sampleUrl = `${GRAPH_URL}/v1.0/oauth2PermissionGrants/${permissionGrantId}`;
genericQuery.sampleHeaders = [{ name: 'ConsistencyLevel', value: 'eventual' }];
patchQuery.selectedVerb = 'PATCH';
// eslint-disable-next-line no-useless-catch

try {
const response = await RevokePermissionsUtil.makeGraphRequest([], patchQuery);
const { error } = response;
if (error) {
const response = await RevokePermissionsUtil.makeGraphRequest([], patchQuery) as { error?: string};
if (response.error) {
return false;
}
return true;
}
catch (error: any) {
throw error;
} catch (error: unknown) {
const err = error as Error;
throw new Error(`Failed to revoke permission: ${err.message}`);
}
}

private static async makeExponentialFetch(scopes: string[], query: IQuery, condition?:
(args?: any) => Promise<boolean>) {
const respHeaders: any = {};
const response = await exponentialFetchRetry(() => makeGraphRequest(scopes)(query),
8, 100, condition);
return parseResponse(response, respHeaders);
(args?: Response) => Promise<boolean>) {
const response = await exponentialFetchRetry(() => permsmakeGraphRequest(scopes)(query), 8, 100, condition);
return response.json()
}

private static async makeGraphRequest(scopes: string[], query: IQuery) {
const respHeaders: any = {};
const response = await makeGraphRequest(scopes)(query);
return parseResponse(response, respHeaders);
const response = await permsmakeGraphRequest(scopes)(query);
return response.json()
}

private trackRevokeConsentEvent = (status: string, permissionObject: any) => {
private trackRevokeConsentEvent = (status: string, permissionObject: string) => {
telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT, {
componentName: componentNames.REVOKE_PERMISSION_CONSENT_BUTTON,
permissionObject,
Expand Down
147 changes: 61 additions & 86 deletions src/app/services/actions/query-action-creator-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function createAnonymousRequest(query: IQuery, proxyUrl: string, queryRun
}

const authToken = `{token:${GRAPH_URL}/}`;
let headers = {
let headers: Record<string, string> = {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
SdkVersion: 'GraphExplorer/4.0',
Expand Down Expand Up @@ -105,31 +105,21 @@ function createAuthenticatedRequest(
}

export function makeGraphRequest(scopes: string[]) {
return async (query: IQuery) => {
let response;

return async function (query: IQuery): Promise<Response> {
const graphRequest: GraphRequest = createAuthenticatedRequest(scopes, query);

switch (query.selectedVerb) {
case 'GET':
response = await graphRequest.get();
break;
case 'POST':
response = await graphRequest.post(query.sampleBody);
break;
case 'PUT':
response = await graphRequest.put(query.sampleBody);
break;
case 'PATCH':
response = await graphRequest.patch(query.sampleBody);
break;
case 'DELETE':
response = await graphRequest.delete();
break;
default:
return;
}
return Promise.resolve(response);
const method = query.selectedVerb.toUpperCase();
const body = query.sampleBody;

const requestMethods: { [key: string]: () => Promise<Response> } = {
'GET': () => graphRequest.get() as Promise<Response>,
'POST': () => graphRequest.post(body) as Promise<Response>,
'PUT': () => graphRequest.put(body) as Promise<Response>,
'PATCH': () => graphRequest.patch(body) as Promise<Response>,
'DELETE': () => graphRequest.delete() as Promise<Response>
};

const response = requestMethods[method] ? await requestMethods[method]() : null;
return response as Response;
};
}

Expand All @@ -146,100 +136,85 @@ export function isBetaURLResponse(json: any) {
return !!json?.account?.[0]?.source?.type?.[0];
}

export function getContentType(headers: any) {
let contentType = null;

if (headers) {
let contentTypes = headers['content-type'];
if (headers instanceof Headers) {
contentTypes = headers.get('content-type');
}
if (contentTypes) {
/* Example: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
* Take the first option after splitting since it is the only value useful in the description of the content
*/
const splitContentTypes = contentTypes.split(';');
if (splitContentTypes.length > 0) {
contentType = splitContentTypes[0].toLowerCase();
}
}
export function getContentType(headers: Record<string, string>) {
let contentType = headers['content-type'] ?? ''
/* Example: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
* Take the first option after splitting since it is the only value useful in the description of the content
*/
const splitContentType = contentType.split(';');
if (splitContentType.length > 0) {
contentType = splitContentType[0].toLowerCase();
}
return contentType;
}

export function isFileResponse(headers: any) {
const contentDisposition = headers['content-disposition'];
export function isFileResponse(headers: Record<string, string>) {
const contentDisposition = headers['content-disposition'] ?? ''
if (contentDisposition) {
const directives = contentDisposition.split(';');
if (directives.contains('attachment')) {
return true;
}
return directives.includes('attachment')
}

// use content type to determine if response is file
const contentType = getContentType(headers);
if (contentType) {
return (
contentType === 'application/octet-stream' ||
return (
contentType === 'application/octet-stream' ||
contentType === 'application/onenote' ||
contentType === 'application/pdf' ||
contentType.includes('application/vnd.') ||
contentType.includes('video/') ||
contentType.includes('audio/')
);
}
return false;
);
}

export async function generateResponseDownloadUrl(
response: Response,
respHeaders: any
) {
respHeaders: Record<string, string>
): Promise<string | null> {
try {
const fileContents = await parseResponse(response, respHeaders);
const contentType = getContentType(respHeaders);
if (fileContents) {
const buffer = await response.arrayBuffer();
const blob = new Blob([buffer], { type: contentType });
return URL.createObjectURL(blob);
}
} catch (error) {
const buffer = await response.arrayBuffer();
const blob = new Blob([buffer], { type: contentType });
return URL.createObjectURL(blob);
} catch {
return null;
}
}

async function tryParseJson(textValue: string) {
try {
return JSON.parse(textValue);
} catch (error) {
} catch {
return textValue;
}
}
/**
* Parses a response to a format that can be displayed in the UI0
* @param response
* @param respHeaders
* @returns
*/
export async function parseResponse(
response: Response, respHeaders: Record<string, string> = {}
): Promise<string | ReadableStream | null> {
const contentType = getContentType(respHeaders);

if (contentType === ContentType.Json) {
const text = await response.text();
return tryParseJson(text);
}

export function parseResponse(
response: Response,
respHeaders: { [key: string]: string } = {}
): Promise<any> {
if (response && response.headers) {
response.headers.forEach((val: string, key: string) => {
respHeaders[key] = val;
});

const contentType = getContentType(response.headers);
switch (contentType) {
case ContentType.Json:
return response.text().then(tryParseJson);
case ContentType.XML:
case ContentType.HTML:
case ContentType.TextCsv:
case ContentType.TextPlain:
return response.text();
const textContentTypes = [
ContentType.XML,
ContentType.HTML,
ContentType.TextCsv,
ContentType.TextPlain
];

default:
return Promise.resolve(response);
}
if (textContentTypes.includes(contentType as ContentType)) {
return response.text();
}
return Promise.resolve(response);

return Promise.resolve(response.body);
}

/**
Expand Down
10 changes: 5 additions & 5 deletions src/app/services/actions/request-history-action-creators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('Request History Action Creators', () => {
index: 0,
statusText: 'Something worked!',
responseHeaders: {},
result: {},
result: '',
url: 'https://graph.microsoft.com/v1.0/me',
method: 'GET',
headers: [],
Expand Down Expand Up @@ -67,7 +67,7 @@ describe('Request History Action Creators', () => {
index: 0,
statusText: 'Something worked!',
responseHeaders: [],
result: {},
result: '',
url: 'https://graph.microsoft.com/v1.0/me',
method: 'GET',
headers: [],
Expand All @@ -79,7 +79,7 @@ describe('Request History Action Creators', () => {
index: 1,
statusText: 'Another history item!',
responseHeaders: [],
result: {},
result: '',
url: 'https://graph.microsoft.com/v1.0/me/events',
method: 'GET',
headers: [],
Expand Down Expand Up @@ -113,7 +113,7 @@ describe('Request History Action Creators', () => {
index: 0,
statusText: 'OK',
responseHeaders: {},
result: {},
result: '',
url: 'https://graph.microsoft.com/v1.0/me',
method: 'GET',
headers: [],
Expand All @@ -125,7 +125,7 @@ describe('Request History Action Creators', () => {
index: 1,
statusText: 'OK',
responseHeaders: {},
result: {},
result: '',
url: 'https://graph.microsoft.com/v1.0/me/events',
method: 'GET',
headers: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ const mockState: ApplicationState = {
graphResponse: {
isLoadingData: false,
response: {
body: undefined,
headers: undefined
body: '',
headers: {}
}
},
snippets: {
Expand Down
Loading

0 comments on commit cd85eca

Please sign in to comment.