зеркало из
1
0
Форкнуть 0
opensource-management-portal/transitional.ts

481 строка
14 KiB
TypeScript

//
// Copyright (c) Microsoft.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
'use strict';
// Transitional interfaces
import { Application, Response, Request } from 'express';
import redis from 'redis';
import { Pool as PostgresPool } from 'pg';
import { IndividualContext } from './user';
import { ICorporateLink } from './business/corporateLink';
import { IApprovalProvider } from './entities/teamJoinApproval/approvalProvider';
import { Operations } from './business/operations';
import { ITokenProvider } from './entities/token';
import { IMailAddressProvider } from './lib/mailAddressProvider';
import { IRepositoryMetadataProvider } from './entities/repositoryMetadata/repositoryMetadataProvider';
import RedisHelper from './lib/caching/redis';
import { ILocalExtensionKeyProvider } from './entities/localExtensionKey';
import { Organization } from './business/organization';
import { IGraphProvider } from './lib/graphProvider';
import { RestLibrary } from './lib/github';
import { Team } from './business/team';
import { IRepositoryCacheProvider } from './entities/repositoryCache/repositoryCacheProvider';
import { IRepositoryCollaboratorCacheProvider } from './entities/repositoryCollaboratorCache/repositoryCollaboratorCacheProvider';
import { ITeamCacheProvider } from './entities/teamCache/teamCacheProvider';
import { ITeamMemberCacheProvider } from './entities/teamMemberCache/teamMemberCacheProvider';
import { IRepositoryTeamCacheProvider } from './entities/repositoryTeamCache/repositoryTeamCacheProvider';
import { IOrganizationMemberCacheProvider } from './entities/organizationMemberCache/organizationMemberCacheProvider';
import { AppPurpose } from './github';
import QueryCache from './business/queryCache';
import { IMailProvider } from './lib/mailProvider';
import { GitHubRepositoryPermission } from './entities/repositoryMetadata/repositoryMetadata';
import { IOrganizationSettingProvider } from './entities/organizationSettings/organizationSettingProvider';
import { ILinkProvider } from './lib/linkProviders';
import { asNumber } from './utils';
import { IAuditLogRecordProvider } from './entities/auditLogRecord/auditLogRecordProvider';
import { IEventRecordProvider } from './entities/events/eventRecordProvider';
import { ICacheHelper } from './lib/caching';
import { ICampaignHelper } from './lib/campaigns';
import { ICorporateContactProvider } from './lib/corporateContactProvider';
import { IQueueProcessor } from './lib/queues';
export interface ICallback<T> {
(error: IReposError, result?: T): void;
}
export type Json =
| string
| number
| boolean
| null
| { [property: string]: Json }
| Json[];
export interface IGetOwnerToken {
(): string;
}
export interface IPurposefulGetAuthorizationHeader {
(purpose: AppPurpose): Promise<IAuthorizationHeaderValue>;
}
export interface IAuthorizationHeaderValue {
value: string;
purpose: AppPurpose;
source?: string;
}
export interface IGetAuthorizationHeader {
(): Promise<IAuthorizationHeaderValue>;
}
export interface IFunctionPromise<T> {
(): Promise<T>;
}
export interface PromiseResolve<T> {
(resolve: T[]): void;
}
export interface PromiseReject {
(reject?: any): void;
}
export interface ICacheOptions {
backgroundRefresh?: any | null | undefined;
maxAgeSeconds?: number | null | undefined;
}
export interface IPagedCacheOptions extends ICacheOptions {
pageRequestDelay?: number | null | undefined; // FUTURE: could be a function, too
}
export interface IPagedCrossOrganizationCacheOptions extends IPagedCacheOptions {
individualMaxAgeSeconds?: number | null | undefined;
individualRequestDelay?: number | null | undefined; // FUTURE: could be a function, too
}
export interface ILocalCacheOptions extends ICacheOptions {
localMaxAgeSeconds?: number;
}
export interface ICacheOptionsPageLimiter extends ICacheOptions {
pageLimit?: number;
}
export interface IMapPlusMetaCost extends Map<any, any> {
headers?: any;
cost?: IReposRestRedisCacheCost;
}
export interface IReposRestRedisCacheCost {
github: {
cacheHits: number;
remainingApiTokens: string;
restApiCalls: number;
usedApiTokens: number;
};
local: {
cacheHits: number;
cacheMisses: number;
};
redis: {
cacheHits: number;
cacheMisses: number;
expireCalls: number;
getCalls: number;
setCalls: number;
};
}
export interface IClassicLink {
ghu: string;
ghid: string; // ?
aadupn: string;
aadname?: string;
aadoid: string;
ghavatar?: string;
githubToken?: string;
githubTokenUpdated?: any;
githubTokenIncreasedScope?: string;
githubTokenIncreasedScopeUpdated?: any;
joined?: Date;
serviceAccount?: boolean;
}
export interface IDictionary<TValue> {
[id: string]: TValue;
}
export interface IProviders {
approvalProvider?: IApprovalProvider;
auditLogRecordProvider?: IAuditLogRecordProvider;
basedir?: string;
eventRecordProvider?: IEventRecordProvider;
campaignStateProvider?: ICampaignHelper;
corporateContactProvider?: ICorporateContactProvider;
config?: any;
healthCheck?: any;
keyEncryptionKeyResolver?: any;
github?: RestLibrary;
graphProvider?: IGraphProvider;
insights?: any;
linkProvider?: ILinkProvider;
localExtensionKeyProvider?: ILocalExtensionKeyProvider;
mailAddressProvider?: IMailAddressProvider;
mailProvider?: IMailProvider;
operations?: Operations;
organizationMemberCacheProvider?: IOrganizationMemberCacheProvider;
organizationSettingsProvider?: IOrganizationSettingProvider;
postgresPool?: PostgresPool;
queryCache?: QueryCache;
webhookQueueProcessor?: IQueueProcessor;
//redis?: RedisHelper;
sessionRedisClient?: redis.RedisClient;
//redisClient?: redis.RedisClient;
cacheProvider?: ICacheHelper;
repositoryCacheProvider?: IRepositoryCacheProvider;
repositoryCollaboratorCacheProvider?: IRepositoryCollaboratorCacheProvider;
repositoryMetadataProvider?: IRepositoryMetadataProvider;
repositoryTeamCacheProvider?: IRepositoryTeamCacheProvider;
session?: any;
teamCacheProvider?: ITeamCacheProvider;
teamMemberCacheProvider?: ITeamMemberCacheProvider;
witnessRedis?: redis.RedisClient;
witnessRedisHelper?: RedisHelper;
tokenProvider?: ITokenProvider;
}
export interface RedisOptions {
auth_pass: string;
detect_buffers: boolean;
tls?: {
servername: string;
}
}
export interface InnerError extends Error {
inner?: Error;
}
export interface IReposError extends Error {
skipLog?: boolean;
status?: any; // status?: number;
code?: any; // not sure this is used any longer by libraries
originalUrl?: any;
detailed?: any;
redirect?: string;
skipOops?:boolean;
fancyLink?: {
link: string;
title: string;
};
innerError?: IReposError;
}
export interface IReposApplication extends Application {
// Standard Express
set(settingName: string, settingValue: any);
// Local things
providers: IProviders;
}
export interface IReposAppContext {
section?: string;
pivotDirectlyToOtherOrg?: string;
releaseTab?: boolean;
organization?: Organization;
}
export interface IReposAppWithTeam extends ReposAppRequest {
teamPermissions?: any;
team2?: Team;
teamUrl: string;
}
export interface ReposAppRequest extends Request {
// passport
isAuthenticated(): boolean;
user: any;
// our extensions
insights?: any;
reposContext?: IReposAppContext;
currentOrganizationMemberships?: any; // needs a redesign
teamsPagerMode?: string;
reposPagerMode?: string;
link?: any; // not sure when this is set
organization?: Organization;
correlationId?: string;
// FUTURE:
apiContext: IndividualContext;
individualContext: IndividualContext;
}
export interface IReposAppResponse extends Response {
}
export interface IReposRequestWithOrganization extends ReposAppRequest {
organization?: any;
}
export interface IRequestTeams extends ReposAppRequest {
team2?: any;
teamUrl?: any;
}
export interface RequestWithSystemwidePermissions extends ReposAppRequest {
systemWidePermissions?: any;
}
export interface IResponseForSettingsPersonalAccessTokens extends Response {
newKey?: string;
}
export function translateNewLinksArrayToOldTemporarily(links: ICorporateLink[]): IClassicLink[] {
return links.map(translateNewLinkFormatToOldTemporarily) as any[] as IClassicLink[];
}
export function translateNewLinkFormatToOldTemporarily(link: ICorporateLink): IClassicLink {
const ancientLink: IClassicLink = {
aadoid: link.corporateId,
aadupn: link.corporateUsername,
ghu: link.thirdPartyUsername,
ghid: link.thirdPartyId,
ghavatar: link.thirdPartyAvatar,
serviceAccount: link.isServiceAccount,
};
return ancientLink;
}
// Not used... yet.
interface ITooManyLinksError extends Error {
links?: any;
tooManyLinks?: boolean;
}
interface IExistingIdentityError extends Error {
anotherAccount?: boolean;
link?: any;
skipLog?: boolean;
}
function tooManyLinksError(self, userLinks, callback) {
const tooManyLinksError: ITooManyLinksError = new Error(`This account has ${userLinks.length} linked GitHub accounts.`);
tooManyLinksError.links = userLinks;
tooManyLinksError.tooManyLinks = true;
return callback(tooManyLinksError, self);
}
function existingGitHubIdentityError(self, link, requestUser, callback) {
const endUser = requestUser.azure.displayName || requestUser.azure.username;
const anotherGitHubAccountError: IExistingIdentityError = new Error(`${endUser}, there is a different GitHub account linked to your corporate identity.`);
anotherGitHubAccountError.anotherAccount = true;
anotherGitHubAccountError.link = link;
anotherGitHubAccountError.skipLog = true;
return callback(anotherGitHubAccountError, self);
}
export function SettleToStateValue<T>(promise: Promise<T>): Promise<ISettledValue<T>> {
return promise.then(value => {
return { value, state: SettledState.Fulfilled };
}, reason => {
return { reason, state: SettledState.Rejected };
});
}
export function permissionsObjectToValue(permissions): GitHubRepositoryPermission {
if (permissions.admin === true) {
return GitHubRepositoryPermission.Admin;
} else if (permissions.push === true) {
return GitHubRepositoryPermission.Push;
} else if (permissions.pull === true) {
return GitHubRepositoryPermission.Pull;
}
throw new Error(`Unsupported GitHubRepositoryPermission value inside permissions`);
}
export function isPermissionBetterThan(currentBest, newConsideration) {
switch (newConsideration) {
case 'admin':
return true;
case 'push':
if (currentBest !== 'admin') {
return true;
}
break;
case 'pull':
if (currentBest === null) {
return true;
}
break;
default:
throw new Error(`Invalid permission type ${newConsideration}`);
}
return false;
}
export function MassagePermissionsToGitHubRepositoryPermission(value: string): GitHubRepositoryPermission {
// collaborator level APIs return a more generic read/write value, lead to some bad caches in the past...
// TODO: support new collaboration values as they come online for Enterprise Cloud!
switch (value) {
case 'write':
case 'push':
return GitHubRepositoryPermission.Push;
case 'admin':
return GitHubRepositoryPermission.Admin;
case 'pull':
case 'read':
return GitHubRepositoryPermission.Pull;
default:
throw new Error(`Invalid ${value} GitHub repository permission [massagePermissionsToGitHubRepositoryPermission]`);
}
}
export interface ISettledValue<T> {
reason?: any;
value?: T;
state: SettledState;
}
export enum SettledState {
Fulfilled = 'fulfilled',
Rejected = 'rejected',
}
export class CreateError {
static CreateStatusCodeError(code: number, message?: string): Error {
const error = new Error(message);
error['status'] = code;
return error;
}
static NotFound(message: string): Error {
return CreateError.CreateStatusCodeError(404, message);
}
static InvalidParameters(message: string): Error {
return CreateError.CreateStatusCodeError(400, message);
}
static NotAuthenticated(message: string): Error {
return CreateError.CreateStatusCodeError(401, message);
}
static NotAuthorized(message: string): Error {
return CreateError.CreateStatusCodeError(403, message);
}
static ServerError(message: string): Error {
return CreateError.CreateStatusCodeError(500, message);
}
}
export class ErrorHelper {
static EnsureHasStatus(error: Error, code: number): Error {
if (!error['status']) {
error['status'] = code;
}
return error;
}
public static HasStatus(error: Error): boolean {
return error && error['status'];
}
public static IsNotFound(error: Error): boolean {
const statusNumber = ErrorHelper.GetStatus(error);
return (statusNumber && statusNumber === 404);
}
public static IsConflict(error: Error): boolean {
const statusNumber = ErrorHelper.GetStatus(error);
if (statusNumber && statusNumber === 409) {
return true;
}
// would be nice to be able to get rid of this clause someday
if (error.message && error.message.includes('already exists')) {
return true;
}
return false;
}
public static GetStatus(error: Error): number {
if (error && error['status']) {
const status = error['status'];
const type = typeof(status);
if (type === 'number') {
return status;
} else if (type === 'string') {
return asNumber(status);
} else {
console.warn(`Unsupported error.status type: ${type}`);
return null;
}
}
return null;
}
}
export function setImmediateAsync(f: IFunctionPromise<void>): void {
const safeCall = () => {
try {
f().catch(error => {
console.warn(`setImmediateAsync caught error: ${error}`);
});
} catch (ignoredFailure) {
console.warn(`setImmediateAsync call error: ${ignoredFailure}`);
}
};
setImmediate(safeCall.bind(null));
}