Custom properties, token refactor, open access teams
- Removes CENTRAL_OPERATIONS_TOKEN - System teams - open access - adds open access concept, which is a broad access team anyone in the org can join without approval - open access teams are not recommended the same way as broad access teams during new repo setup - TypeScript: prefer types to interfaces - GitHub Apps and REST APIs: - Simplifying bound function calls - Relocated app and token management files - Improves types for header/tokens - Allow custom app purposes to retrieve app token instances - Custom app purpose debug display fix - PAT/app token type identification helper method - Collections now expose "collectAllPages" and "collectAllPagesViaHttpGet" to move specific code out of the file - Fix for custom apps initialized after startup - Custom Properties Beta support
This commit is contained in:
Родитель
9bf880975f
Коммит
00eb7c2ddc
|
@ -1,4 +1,3 @@
|
|||
GITHUB_CENTRAL_OPERATIONS_TOKEN=
|
||||
GITHUB_CLIENT_ID=
|
||||
GITHUB_CLIENT_SECRET=
|
||||
GITHUB_CALLBACK_URL=http://localhost:3000/auth/github/callback
|
||||
|
|
|
@ -115,7 +115,6 @@ router.get(
|
|||
}
|
||||
const userAggregateContext = activeContext.aggregations;
|
||||
const maintainedTeams = new Set<string>();
|
||||
const broadTeams = new Set<number>(req.organization.broadAccessTeams);
|
||||
const userTeams = userAggregateContext.reduceOrganizationTeams(
|
||||
organization,
|
||||
await userAggregateContext.teams()
|
||||
|
|
|
@ -51,6 +51,7 @@ router.get(
|
|||
const userAggregateContext = req.apiContext.aggregations;
|
||||
const maintainedTeams = new Set<string>();
|
||||
const broadTeams = new Set<number>(req.organization.broadAccessTeams);
|
||||
const openAccessTeams = new Set<number>(req.organization.openAccessTeams);
|
||||
const userTeams = userAggregateContext.reduceOrganizationTeams(
|
||||
organization,
|
||||
await userAggregateContext.teams()
|
||||
|
@ -62,6 +63,7 @@ router.get(
|
|||
const personalizedTeams = Array.from(combinedTeams.values()).map((combinedTeam) => {
|
||||
return {
|
||||
broad: broadTeams.has(Number(combinedTeam.id)),
|
||||
isOpenAccessTeam: openAccessTeams.has(Number(combinedTeam.id)),
|
||||
description: combinedTeam.description,
|
||||
id: Number(combinedTeam.id),
|
||||
name: combinedTeam.name,
|
||||
|
@ -86,6 +88,7 @@ router.get(
|
|||
const queryCache = providers.queryCache;
|
||||
const organization = req.organization as Organization;
|
||||
const broadTeams = new Set(organization.broadAccessTeams);
|
||||
const openAccessTeams = new Set<number>(req.organization.openAccessTeams);
|
||||
if (req.query.refresh === undefined && queryCache && queryCache.supportsTeams) {
|
||||
// Use the newer method in this case...
|
||||
const organizationTeams = await queryCache.organizationTeams(organization.id.toString());
|
||||
|
@ -96,6 +99,9 @@ router.get(
|
|||
if (broadTeams.has(Number(t.id))) {
|
||||
t['broad'] = true;
|
||||
}
|
||||
if (openAccessTeams.has(Number(t.id))) {
|
||||
t['openAccess'] = true;
|
||||
}
|
||||
return t;
|
||||
}),
|
||||
}) as unknown as void;
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as common from './common';
|
|||
import { wrapError } from '../utils';
|
||||
import { corporateLinkToJson } from './corporateLink';
|
||||
import { Organization } from './organization';
|
||||
import { AppPurpose } from './githubApps';
|
||||
import { AppPurpose } from '../lib/github/appPurposes';
|
||||
import { ILinkProvider } from '../lib/linkProviders';
|
||||
import { CacheDefault, getMaxAgeSeconds } from '.';
|
||||
import {
|
||||
|
@ -18,7 +18,7 @@ import {
|
|||
CoreCapability,
|
||||
ICacheOptions,
|
||||
ICorporateLink,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
IGitHubAccountDetails,
|
||||
IOperationsInstance,
|
||||
IOperationsLinks,
|
||||
|
@ -46,7 +46,7 @@ const secondaryAccountProperties = [];
|
|||
|
||||
export class Account {
|
||||
private _operations: IOperationsInstance;
|
||||
private _getAuthorizationHeader: IGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: GetAuthorizationHeader;
|
||||
|
||||
private _link: ICorporateLink;
|
||||
private _id: number;
|
||||
|
@ -137,7 +137,7 @@ export class Account {
|
|||
return this._originalEntity ? this._originalEntity.name : undefined;
|
||||
}
|
||||
|
||||
constructor(entity, operations: IOperationsInstance, getAuthorizationHeader: IGetAuthorizationHeader) {
|
||||
constructor(entity, operations: IOperationsInstance, getAuthorizationHeader: GetAuthorizationHeader) {
|
||||
common.assignKnownFieldsPrefixed(
|
||||
this,
|
||||
entity,
|
||||
|
@ -150,7 +150,7 @@ export class Account {
|
|||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
}
|
||||
|
||||
overrideAuthorization(getAuthorizationHeader: IGetAuthorizationHeader) {
|
||||
overrideAuthorization(getAuthorizationHeader: GetAuthorizationHeader) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
}
|
||||
|
||||
|
@ -571,11 +571,8 @@ export class Account {
|
|||
return { history, error };
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose): IGetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { OrganizationSetting } from '../entities/organizationSettings/organizati
|
|||
import {
|
||||
IOperationsGitHubRestLibrary,
|
||||
IOperationsDefaultCacheTimes,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
IGitHubAppInstallation,
|
||||
ICacheOptions,
|
||||
} from '../interfaces';
|
||||
|
@ -50,7 +50,7 @@ export default class GitHubApplication {
|
|||
public id: number,
|
||||
public slug: string,
|
||||
public friendlyName: string,
|
||||
private getAuthorizationHeader: IGetAuthorizationHeader
|
||||
private getAuthorizationHeader: GetAuthorizationHeader
|
||||
) {}
|
||||
|
||||
static PrimaryInstallationProperties = primaryInstallationProperties;
|
||||
|
@ -115,7 +115,7 @@ export default class GitHubApplication {
|
|||
async getInstallations(options?: ICacheOptions): Promise<IGitHubAppInstallation[]> {
|
||||
options = options || {};
|
||||
const operations = this.operations;
|
||||
const getAuthorizationHeader = this.getAuthorizationHeader.bind(this) as IGetAuthorizationHeader;
|
||||
const getAuthorizationHeader = this.getAuthorizationHeader.bind(this) as GetAuthorizationHeader;
|
||||
const github = operations.github;
|
||||
const caching = {
|
||||
maxAgeSeconds: options.maxAgeSeconds || operations.defaults.orgRepoDetailsStaleSeconds, // borrowing from another value
|
||||
|
@ -132,8 +132,8 @@ export default class GitHubApplication {
|
|||
return installations;
|
||||
}
|
||||
|
||||
private authorize(): IGetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this.getAuthorizationHeader.bind(this) as IGetAuthorizationHeader;
|
||||
private authorize(): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this.getAuthorizationHeader.bind(this) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import { Organization } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
throwIfNotGitHubCapable,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
import {
|
||||
decorateIterable,
|
||||
|
@ -44,15 +44,15 @@ export class OrganizationDomains {
|
|||
private _organization: Organization;
|
||||
private _operations: IOperationsInstance;
|
||||
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _purpose: AppPurpose;
|
||||
|
||||
constructor(
|
||||
organization: Organization,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader
|
||||
) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
this._getSpecificAuthorizationHeader = getSpecificAuthorizationHeader;
|
||||
|
@ -110,19 +110,16 @@ export class OrganizationDomains {
|
|||
}
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose = this._purpose): IGetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose = this._purpose): GetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,13 @@
|
|||
//
|
||||
|
||||
import { OrganizationSetting } from '../../entities/organizationSettings/organizationSetting';
|
||||
import { GitHubAppAuthenticationType, AppPurpose, ICustomAppPurpose, AppPurposeTypes } from '../githubApps';
|
||||
import { GitHubTokenManager } from '../githubApps/tokenManager';
|
||||
import {
|
||||
GitHubAppAuthenticationType,
|
||||
AppPurpose,
|
||||
ICustomAppPurpose,
|
||||
AppPurposeTypes,
|
||||
} from '../../lib/github/appPurposes';
|
||||
import { GitHubTokenManager } from '../../lib/github/tokenManager';
|
||||
import {
|
||||
IProviders,
|
||||
ICacheDefaultTimes,
|
||||
|
@ -19,9 +24,11 @@ import {
|
|||
throwIfNotGitHubCapable,
|
||||
throwIfNotCapable,
|
||||
IOperationsCentralOperationsToken,
|
||||
IAuthorizationHeaderValue,
|
||||
AuthorizationHeaderValue,
|
||||
SiteConfiguration,
|
||||
ExecutionEnvironment,
|
||||
IPagedCacheOptions,
|
||||
ICacheOptionsWithPurpose,
|
||||
} from '../../interfaces';
|
||||
import { RestLibrary } from '../../lib/github';
|
||||
import { CreateError } from '../../transitional';
|
||||
|
@ -32,6 +39,28 @@ import GitHubApplication from '../application';
|
|||
import Debug from 'debug';
|
||||
const debugGitHubTokens = Debug('github:tokens');
|
||||
|
||||
const symbolCost = Symbol('cost');
|
||||
const symbolHeaders = Symbol('headers');
|
||||
|
||||
export function symbolizeApiResponse<T>(response: any): T {
|
||||
if (response && response.headers) {
|
||||
response[symbolHeaders] = response.headers;
|
||||
delete response.headers;
|
||||
}
|
||||
if (response && response.cost) {
|
||||
response[symbolCost] = response.cost;
|
||||
delete response.cost;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
export function getApiSymbolMetadata(response: any) {
|
||||
if (response) {
|
||||
return { headers: response[symbolHeaders], cost: response[symbolCost] };
|
||||
}
|
||||
throw CreateError.ParameterRequired('response');
|
||||
}
|
||||
|
||||
export interface IOperationsCoreOptions {
|
||||
github: RestLibrary;
|
||||
providers: IProviders;
|
||||
|
@ -63,6 +92,7 @@ export enum CacheDefault {
|
|||
teamDetailStaleSeconds = 'teamDetailStaleSeconds',
|
||||
orgRepoWebhooksStaleSeconds = 'orgRepoWebhooksStaleSeconds',
|
||||
teamRepositoryPermissionStaleSeconds = 'teamRepositoryPermissionStaleSeconds',
|
||||
defaultStaleSeconds = 'defaultStaleSeconds',
|
||||
}
|
||||
|
||||
// defaults could move to configuration alternatively
|
||||
|
@ -90,6 +120,7 @@ const defaults: ICacheDefaultTimes = {
|
|||
[CacheDefault.teamDetailStaleSeconds]: 60 * 60 * 2 /* 2h */,
|
||||
[CacheDefault.orgRepoWebhooksStaleSeconds]: 60 * 60 * 8 /* 8h */,
|
||||
[CacheDefault.teamRepositoryPermissionStaleSeconds]: 0 /* 0m */,
|
||||
[CacheDefault.defaultStaleSeconds]: 60 /* 1m */,
|
||||
};
|
||||
|
||||
export const DefaultPageSize = 100;
|
||||
|
@ -108,6 +139,46 @@ export function getPageSize(operations: IOperationsInstance, options?: IOptionWi
|
|||
return DefaultPageSize;
|
||||
}
|
||||
|
||||
export function createCacheOptions(
|
||||
operations: IOperationsInstance,
|
||||
options?: ICacheOptions,
|
||||
cacheDefault: CacheDefault = CacheDefault.defaultStaleSeconds
|
||||
) {
|
||||
const cacheOptions: ICacheOptions = {
|
||||
maxAgeSeconds: getMaxAgeSeconds(operations, cacheDefault, options, 60),
|
||||
};
|
||||
if (options.backgroundRefresh !== undefined) {
|
||||
cacheOptions.backgroundRefresh = options.backgroundRefresh;
|
||||
}
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
export function createPagedCacheOptions(
|
||||
operations: IOperationsInstance,
|
||||
options?: IPagedCacheOptions,
|
||||
cacheDefault: CacheDefault = CacheDefault.defaultStaleSeconds
|
||||
) {
|
||||
const cacheOptions: IPagedCacheOptions = {
|
||||
maxAgeSeconds: getMaxAgeSeconds(operations, cacheDefault, options, 60),
|
||||
};
|
||||
if (options.pageRequestDelay !== undefined) {
|
||||
cacheOptions.pageRequestDelay = options.pageRequestDelay;
|
||||
}
|
||||
if (options.backgroundRefresh !== undefined) {
|
||||
cacheOptions.backgroundRefresh = options.backgroundRefresh;
|
||||
}
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
export function popPurpose(options: ICacheOptionsWithPurpose, defaultPurpose: AppPurposeTypes) {
|
||||
if (options.purpose) {
|
||||
const purpose = options.purpose;
|
||||
delete options.purpose;
|
||||
return purpose;
|
||||
}
|
||||
return defaultPurpose;
|
||||
}
|
||||
|
||||
export function getMaxAgeSeconds(
|
||||
operations: IOperationsInstance,
|
||||
cacheDefault: CacheDefault,
|
||||
|
@ -197,7 +268,7 @@ export abstract class OperationsCore
|
|||
async getAccountByUsername(username: string, options?: ICacheOptions): Promise<Account> {
|
||||
options = options || {};
|
||||
const operations = throwIfNotGitHubCapable(this);
|
||||
const centralOperations = throwIfNotCapable<IOperationsCentralOperationsToken>(
|
||||
const ops = throwIfNotCapable<IOperationsCentralOperationsToken>(
|
||||
this,
|
||||
CoreCapability.GitHubCentralOperations
|
||||
);
|
||||
|
@ -214,10 +285,9 @@ export abstract class OperationsCore
|
|||
cacheOptions.backgroundRefresh = options.backgroundRefresh;
|
||||
}
|
||||
try {
|
||||
const getHeaderFunction = centralOperations.getCentralOperationsToken();
|
||||
const authorizationHeader = await getHeaderFunction(AppPurpose.Data);
|
||||
const getHeaderFunction = ops.getPublicAuthorizationToken();
|
||||
const entity = await operations.github.call(
|
||||
authorizationHeader,
|
||||
getHeaderFunction,
|
||||
'users.getByUsername',
|
||||
parameters,
|
||||
cacheOptions
|
||||
|
@ -337,17 +407,16 @@ export abstract class OperationsCore
|
|||
organizationName: string,
|
||||
organizationSettings: OrganizationSetting,
|
||||
legacyOwnerToken: string,
|
||||
centralOperationsFallbackToken: string,
|
||||
appAuthenticationType: GitHubAppAuthenticationType,
|
||||
purpose: AppPurposeTypes
|
||||
): Promise<IAuthorizationHeaderValue> {
|
||||
): Promise<AuthorizationHeaderValue> {
|
||||
const customPurpose = purpose as ICustomAppPurpose;
|
||||
const isCustomPurpose = customPurpose?.isCustomAppPurpose === true;
|
||||
if (
|
||||
!isCustomPurpose &&
|
||||
!this.tokenManager.organizationSupportsAnyPurpose(organizationName, organizationSettings)
|
||||
) {
|
||||
const legacyTokenValue = legacyOwnerToken || centralOperationsFallbackToken;
|
||||
const legacyTokenValue = legacyOwnerToken;
|
||||
if (!legacyTokenValue) {
|
||||
throw new Error(
|
||||
`Organization ${organizationName} is not configured with a GitHub app, Personal Access Token ownerToken configuration value, or a fallback central operations token for the ${
|
||||
|
@ -358,7 +427,7 @@ export abstract class OperationsCore
|
|||
return {
|
||||
value: `token ${legacyTokenValue}`,
|
||||
purpose: null,
|
||||
source: legacyOwnerToken ? 'legacyOwnerToken' : 'centralOperationsFallbackToken',
|
||||
source: 'legacyOwnerToken',
|
||||
};
|
||||
}
|
||||
if (!purpose) {
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
|
||||
import axios from 'axios';
|
||||
import throat from 'throat';
|
||||
import { shuffle } from 'lodash';
|
||||
|
||||
import { Account } from '../account';
|
||||
import { GraphManager } from '../graphManager';
|
||||
import { GitHubOrganizationResponse, Organization } from '../organization';
|
||||
import { GitHubTokenManager } from '../githubApps/tokenManager';
|
||||
import { GitHubTokenManager } from '../../lib/github/tokenManager';
|
||||
import RenderHtmlMail from '../../lib/emailRender';
|
||||
import { wrapError, sortByCaseInsensitive } from '../../utils';
|
||||
import { Repository } from '../repository';
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
GitHubAppAuthenticationType,
|
||||
GitHubAppPurposes,
|
||||
IGitHubAppConfiguration,
|
||||
} from '../githubApps';
|
||||
} from '../../lib/github/appPurposes';
|
||||
import {
|
||||
OrganizationFeature,
|
||||
OrganizationSetting,
|
||||
|
@ -42,7 +43,7 @@ import {
|
|||
ICreateLinkOptions,
|
||||
ICrossOrganizationMembershipByOrganization,
|
||||
ICrossOrganizationTeamMembership,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
IGitHubAppInstallation,
|
||||
IMapPlusMetaCost,
|
||||
IOperationsCentralOperationsToken,
|
||||
|
@ -56,7 +57,7 @@ import {
|
|||
IOperationsTemplates,
|
||||
IPagedCrossOrganizationCacheOptions,
|
||||
IPromisedLinks,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
ISupportedLinkTypeOutcome,
|
||||
IUnlinkMailStatus,
|
||||
NoCacheNoBackground,
|
||||
|
@ -68,6 +69,7 @@ import { Team } from '../team';
|
|||
import { IRepositoryMetadataProvider } from '../../entities/repositoryMetadata/repositoryMetadataProvider';
|
||||
import { isAuthorizedSystemAdministrator } from './administration';
|
||||
import type { ConfigGitHubOrganizationsSpecializedList } from '../../config/github.organizations.types';
|
||||
import { type GitHubTokenType, getGitHubTokenTypeFromValue } from '../../lib/github/appTokens';
|
||||
|
||||
export * from './core';
|
||||
|
||||
|
@ -116,7 +118,7 @@ export class Operations
|
|||
private _invisibleOrganizations: Map<string, Organization>;
|
||||
private _uncontrolledOrganizations: Map<string, Organization>;
|
||||
private _organizationOriginalNames: any;
|
||||
private _organizationNamesWithAuthorizationHeaders: Map<string, IPurposefulGetAuthorizationHeader>;
|
||||
private _organizationNamesWithAuthorizationHeaders: Map<string, PurposefulGetAuthorizationHeader>;
|
||||
private _defaultPageSize: number;
|
||||
private _organizationIds: Map<number, Organization>;
|
||||
private _organizationSettings: OrganizationSetting[];
|
||||
|
@ -172,10 +174,10 @@ export class Operations
|
|||
}
|
||||
}
|
||||
this._tokenManager = new GitHubTokenManager({
|
||||
operations: this,
|
||||
configurations: purposesToConfigurations,
|
||||
executionEnvironment: options.executionEnvironment,
|
||||
});
|
||||
GitHubTokenManager.RegisterManagerForOperations(this, this._tokenManager);
|
||||
this._dynamicOrganizationIds = new Set();
|
||||
this._organizationSettings = [];
|
||||
}
|
||||
|
@ -339,7 +341,6 @@ export class Operations
|
|||
private createOrganization(
|
||||
name: string,
|
||||
settings: OrganizationSetting,
|
||||
centralOperationsFallbackToken: string,
|
||||
appAuthenticationType: GitHubAppAuthenticationType
|
||||
): Organization {
|
||||
name = name.toLowerCase();
|
||||
|
@ -355,20 +356,12 @@ export class Operations
|
|||
this,
|
||||
name,
|
||||
settings,
|
||||
this.getAuthorizationHeader.bind(this, name, settings, ownerToken, appAuthenticationType),
|
||||
this.getAuthorizationHeader.bind(
|
||||
this,
|
||||
name,
|
||||
settings,
|
||||
ownerToken,
|
||||
centralOperationsFallbackToken,
|
||||
appAuthenticationType
|
||||
),
|
||||
this.getAuthorizationHeader.bind(
|
||||
this,
|
||||
name,
|
||||
settings,
|
||||
ownerToken,
|
||||
centralOperationsFallbackToken,
|
||||
GitHubAppAuthenticationType.ForceSpecificInstallation
|
||||
),
|
||||
hasDynamicSettings
|
||||
|
@ -379,7 +372,6 @@ export class Operations
|
|||
if (!this._organizations) {
|
||||
const organizations = new Map<string, Organization>();
|
||||
const names = this.organizationNames;
|
||||
const centralOperationsToken = this.config.github.operations.centralOperationsToken;
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
let settings: OrganizationSetting = null;
|
||||
|
@ -395,7 +387,6 @@ export class Operations
|
|||
const organization = this.createOrganization(
|
||||
name,
|
||||
settings,
|
||||
centralOperationsToken,
|
||||
GitHubAppAuthenticationType.BestAvailable
|
||||
);
|
||||
organizations.set(name, organization);
|
||||
|
@ -416,11 +407,9 @@ export class Operations
|
|||
for (let i = 0; i < list.length; i++) {
|
||||
const settings = list[i];
|
||||
if (settings && settings.name && settings.name.toLowerCase() === lowercase) {
|
||||
const centralOperationsToken = this.config.github.operations.centralOperationsToken;
|
||||
return this.createOrganization(
|
||||
lowercase,
|
||||
OrganizationSetting.CreateFromStaticSettings(settings),
|
||||
centralOperationsToken,
|
||||
GitHubAppAuthenticationType.BestAvailable
|
||||
);
|
||||
}
|
||||
|
@ -441,7 +430,6 @@ export class Operations
|
|||
return this.createOrganization(
|
||||
settings.organizationName.toLowerCase(),
|
||||
settings,
|
||||
null,
|
||||
GitHubAppAuthenticationType.BestAvailable
|
||||
);
|
||||
}
|
||||
|
@ -474,7 +462,7 @@ export class Operations
|
|||
dynamicSettings = options.settings;
|
||||
}
|
||||
const authenticationType = options?.authenticationType || GitHubAppAuthenticationType.BestAvailable;
|
||||
const organization = this.createOrganization(name, dynamicSettings, null, authenticationType);
|
||||
const organization = this.createOrganization(name, dynamicSettings, authenticationType);
|
||||
if (!options || options?.storeInstanceByName) {
|
||||
this._invisibleOrganizations.set(name, organization);
|
||||
}
|
||||
|
@ -492,11 +480,9 @@ export class Operations
|
|||
}
|
||||
const emptySettings = new OrganizationSetting();
|
||||
emptySettings.operationsNotes = `Uncontrolled Organization - ${organizationName}`;
|
||||
const centralOperationsToken = this.config.github.operations.centralOperationsToken;
|
||||
const org = this.createOrganization(
|
||||
organizationName,
|
||||
emptySettings,
|
||||
centralOperationsToken,
|
||||
GitHubAppAuthenticationType.ForceSpecificInstallation
|
||||
);
|
||||
this._uncontrolledOrganizations.set(organizationName, org);
|
||||
|
@ -506,16 +492,18 @@ export class Operations
|
|||
|
||||
getPublicOnlyAccessOrganization(organizationName: string, organizationId?: number): Organization {
|
||||
organizationName = organizationName.toLowerCase();
|
||||
const emptySettings = new OrganizationSetting();
|
||||
emptySettings.operationsNotes = `Uncontrolled public organization - ${organizationName}`;
|
||||
const publicAccessToken = this.config.github.operations.publicAccessToken;
|
||||
if (!publicAccessToken) {
|
||||
throw new Error('not configured for public read-only tokens');
|
||||
throw CreateError.InvalidParameters('not configured for public read-only tokens');
|
||||
}
|
||||
const emptySettings = OrganizationSetting.CreateEmptyWithOldToken(
|
||||
publicAccessToken,
|
||||
`Uncontrolled public organization - ${organizationName}`,
|
||||
organizationId
|
||||
);
|
||||
const org = this.createOrganization(
|
||||
organizationName,
|
||||
emptySettings,
|
||||
publicAccessToken,
|
||||
GitHubAppAuthenticationType.ForceSpecificInstallation
|
||||
);
|
||||
this._uncontrolledOrganizations.set(organizationName, org);
|
||||
|
@ -593,7 +581,7 @@ export class Operations
|
|||
|
||||
get organizationNamesWithAuthorizationHeaders() {
|
||||
if (!this._organizationNamesWithAuthorizationHeaders) {
|
||||
const tokens = new Map<string, IPurposefulGetAuthorizationHeader>();
|
||||
const tokens = new Map<string, PurposefulGetAuthorizationHeader>();
|
||||
const visited = new Set<string>();
|
||||
for (const entry of this._organizationSettings) {
|
||||
const lowercase = entry.organizationName.toLowerCase();
|
||||
|
@ -825,7 +813,8 @@ export class Operations
|
|||
const lc = name.toLowerCase();
|
||||
const organization = this.organizations.get(lc);
|
||||
if (!organization) {
|
||||
throw CreateError.NotFound(`Could not find configuration for the "${name}" organization.`);
|
||||
// will this impact things?
|
||||
throw CreateError.InvalidParameters(`Could not find configuration for the "${name}" organization.`);
|
||||
}
|
||||
return organization;
|
||||
}
|
||||
|
@ -913,6 +902,45 @@ export class Operations
|
|||
}
|
||||
|
||||
async getOrganizationProfileById(id: number, options?: ICacheOptions): Promise<GitHubOrganizationResponse> {
|
||||
options = options || {};
|
||||
if (!id) {
|
||||
throw new Error('Must provide a repository ID to retrieve the repository.');
|
||||
}
|
||||
const organization = this._organizationIds.get(id);
|
||||
return this._getOrganizationProfileById(id, organization ? id : null, options);
|
||||
}
|
||||
|
||||
async getOrganizationPublicProfileById(
|
||||
id: number,
|
||||
options?: ICacheOptions
|
||||
): Promise<GitHubOrganizationResponse> {
|
||||
options = options || {};
|
||||
if (!id) {
|
||||
throw new Error('Must provide a repository ID to retrieve the repository.');
|
||||
}
|
||||
let lookupId: number | null = this._organizationIds.get(id) ? id : null;
|
||||
if (lookupId) {
|
||||
const allIdsExcludingOrg = this.getOrganizationIds().filter((orgId) => orgId !== id);
|
||||
const shuffledIds = shuffle(allIdsExcludingOrg);
|
||||
if (shuffledIds.length > 0) {
|
||||
lookupId = shuffledIds[0];
|
||||
}
|
||||
}
|
||||
if (lookupId === null) {
|
||||
throw CreateError.InvalidParameters(
|
||||
'This approach requires configuring at least two organizations (getOrganizationPublicProfileById).'
|
||||
);
|
||||
}
|
||||
return this._getOrganizationProfileById(id, lookupId, options);
|
||||
}
|
||||
|
||||
private async _getOrganizationProfileById(
|
||||
id: number,
|
||||
lookupUsingIdOrCentralToken: number | null,
|
||||
options?: ICacheOptions
|
||||
): Promise<GitHubOrganizationResponse> {
|
||||
// EMU note: you need to use an EMU-installed app vs public...
|
||||
// Cache note: this will be a cache miss if you switch between public/non-public entrypoints
|
||||
options = options || {};
|
||||
if (!id) {
|
||||
throw new Error('Must provide a repository ID to retrieve the repository.');
|
||||
|
@ -926,13 +954,15 @@ export class Operations
|
|||
if (options.backgroundRefresh !== undefined) {
|
||||
cacheOptions.backgroundRefresh = options.backgroundRefresh;
|
||||
}
|
||||
const organization = this._organizationIds.get(lookupUsingIdOrCentralToken);
|
||||
let header: GetAuthorizationHeader = null;
|
||||
if (organization) {
|
||||
header = organization.getAuthorizationHeader() as GetAuthorizationHeader; // fallback will happen
|
||||
} else {
|
||||
header = this.getPublicAuthorizationToken();
|
||||
}
|
||||
try {
|
||||
const entity = await this.github.request(
|
||||
this.getCentralOperationsToken(),
|
||||
'GET /organizations/:id',
|
||||
parameters,
|
||||
cacheOptions
|
||||
);
|
||||
const entity = await this.github.request(header, 'GET /organizations/:id', parameters, cacheOptions);
|
||||
return entity;
|
||||
} catch (error) {
|
||||
if (error.status && error.status === 404) {
|
||||
|
@ -1278,19 +1308,32 @@ export class Operations
|
|||
return false;
|
||||
}
|
||||
|
||||
getCentralOperationsToken(): IGetAuthorizationHeader {
|
||||
const func = getCentralOperationsAuthorizationHeader.bind(null, this) as IGetAuthorizationHeader;
|
||||
return func;
|
||||
getPublicReadOnlyStaticToken(): GetAuthorizationHeader {
|
||||
const { config } = this.providers;
|
||||
if (config?.github?.operations?.publicAccessToken) {
|
||||
const capturedToken = config.github.operations.publicAccessToken;
|
||||
return async () => {
|
||||
return {
|
||||
value: `token ${capturedToken}`,
|
||||
purpose: null,
|
||||
source: 'public read-only token',
|
||||
};
|
||||
};
|
||||
}
|
||||
throw CreateError.InvalidParameters('No configured read-only static token');
|
||||
}
|
||||
|
||||
getPublicReadOnlyToken(): IGetAuthorizationHeader {
|
||||
const func = getReadOnlyAuthorizationHeader.bind(null, this) as IGetAuthorizationHeader;
|
||||
return func;
|
||||
getPublicAuthorizationToken(): GetAuthorizationHeader {
|
||||
try {
|
||||
return this._tokenManager.getAuthorizationHeaderForAnyApp.bind(this._tokenManager);
|
||||
} catch (error) {
|
||||
return this.getPublicReadOnlyStaticToken();
|
||||
}
|
||||
}
|
||||
|
||||
getAccount(id: string) {
|
||||
const entity = { id };
|
||||
return new Account(entity, this, getCentralOperationsAuthorizationHeader.bind(null, this));
|
||||
return new Account(entity, this, this.getPublicAuthorizationToken.bind(this));
|
||||
}
|
||||
|
||||
async getAccountWithDetailsAndLink(id: string): Promise<Account> {
|
||||
|
@ -1299,14 +1342,27 @@ export class Operations
|
|||
}
|
||||
|
||||
async getAuthenticatedAccount(token: string): Promise<Account> {
|
||||
// Returns an account instance based on the account identified in the token.
|
||||
const github = this.github;
|
||||
const parameters = {};
|
||||
const fullToken = `token ${token}`;
|
||||
let tokenType: GitHubTokenType = null;
|
||||
try {
|
||||
const entity = await github.post(`token ${token}`, 'users.getAuthenticated', parameters);
|
||||
const account = new Account(entity, this, getCentralOperationsAuthorizationHeader.bind(null, this));
|
||||
tokenType = getGitHubTokenTypeFromValue(fullToken);
|
||||
} catch (error) {
|
||||
// ignoring any issue here on identifying the type of token
|
||||
}
|
||||
try {
|
||||
const entity = await github.post(fullToken, 'users.getAuthenticated', parameters);
|
||||
const account = new Account(entity, this, this.getPublicAuthorizationToken.bind(this));
|
||||
return account;
|
||||
} catch (error) {
|
||||
throw wrapError(error, 'Could not get details about the authenticated account');
|
||||
throw wrapError(
|
||||
error,
|
||||
`Could not get details about the authenticated account${
|
||||
tokenType ? ' using token type "' + tokenType + '"' : '.'
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1420,42 +1476,6 @@ async function fireEvent(config, configurationName, value): Promise<IFireEventRe
|
|||
return results;
|
||||
}
|
||||
|
||||
export function getReadOnlyAuthorizationHeader(self: Operations): IPurposefulGetAuthorizationHeader {
|
||||
const s = (self || this) as Operations;
|
||||
if (s.config?.github?.operations?.publicAccessToken) {
|
||||
const capturedToken = s.config.github.operations.publicAccessToken;
|
||||
return async () => {
|
||||
return {
|
||||
value: `token ${capturedToken}`,
|
||||
purpose: null,
|
||||
source: 'public read-only token',
|
||||
};
|
||||
};
|
||||
} else {
|
||||
throw new Error('No public read-only token configured.');
|
||||
}
|
||||
}
|
||||
|
||||
export function getCentralOperationsAuthorizationHeader(self: Operations): IPurposefulGetAuthorizationHeader {
|
||||
const s = (self || this) as Operations;
|
||||
if (s.config.github && s.config.github.operations && s.config.github.operations.centralOperationsToken) {
|
||||
const capturedToken = s.config.github.operations.centralOperationsToken;
|
||||
return async () => {
|
||||
return {
|
||||
value: `token ${capturedToken}`,
|
||||
purpose: null, // legacy
|
||||
source: 'central operations token',
|
||||
};
|
||||
};
|
||||
} else if (s.getOrganizations().length === 0) {
|
||||
throw new Error('No central operations token nor any organizations configured.');
|
||||
}
|
||||
// Fallback to the first configured organization as a convenience
|
||||
// CONSIDER: would randomizing the organization be better, or a priority based on known-rate limit remaining?
|
||||
const firstOrganization = s.getOrganizations()[0];
|
||||
return firstOrganization.getAuthorizationHeader();
|
||||
}
|
||||
|
||||
function crossOrganizationResults(operations: Operations, results, keyProperty) {
|
||||
keyProperty = keyProperty || 'id';
|
||||
const map: IMapPlusMetaCost = new Map();
|
||||
|
|
|
@ -13,11 +13,11 @@ import { Repository } from './repository';
|
|||
import { wrapError } from '../utils';
|
||||
import { StripGitHubEntity } from '../lib/github/restApi';
|
||||
import { GitHubResponseType } from '../lib/github/endpointEntities';
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import {
|
||||
OrganizationFeature,
|
||||
OrganizationSetting,
|
||||
SpecialTeam,
|
||||
SystemTeam,
|
||||
} from '../entities/organizationSettings/organizationSetting';
|
||||
import { createOrganizationSudoInstance, IOrganizationSudo } from '../features';
|
||||
import { CacheDefault, getMaxAgeSeconds, getPageSize, OperationsCore } from './operations/core';
|
||||
|
@ -28,12 +28,12 @@ import {
|
|||
GitHubRepositoryVisibility,
|
||||
IAccountBasics,
|
||||
IAddOrganizationMembershipOptions,
|
||||
IAuthorizationHeaderValue,
|
||||
AuthorizationHeaderValue,
|
||||
ICacheOptions,
|
||||
ICacheOptionsWithPurpose,
|
||||
ICorporateLink,
|
||||
ICreateRepositoryResult,
|
||||
IGetAuthorizationHeader,
|
||||
type GetAuthorizationHeader,
|
||||
IGetOrganizationAuditLogOptions,
|
||||
IGetOrganizationMembersOptions,
|
||||
IGitHubAccountDetails,
|
||||
|
@ -49,7 +49,7 @@ import {
|
|||
IOrganizationMemberPair,
|
||||
IOrganizationMembership,
|
||||
IPagedCacheOptions,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
type PurposefulGetAuthorizationHeader,
|
||||
IReposError,
|
||||
IReposRestRedisCacheCost,
|
||||
NoCacheNoBackground,
|
||||
|
@ -64,10 +64,11 @@ import { CreateError, ErrorHelper } from '../transitional';
|
|||
import { jsonError } from '../middleware';
|
||||
import getCompanySpecificDeployment from '../middleware/companySpecificDeployment';
|
||||
import { ConfigGitHubTemplates } from '../config/github.templates.types';
|
||||
import { GitHubTokenManager } from './githubApps/tokenManager';
|
||||
import { GitHubTokenManager } from '../lib/github/tokenManager';
|
||||
import { OrganizationProjects } from './projects';
|
||||
import { OrganizationDomains } from './domains';
|
||||
import { OrganizationCopilot } from './organizationCopilot';
|
||||
import { OrganizationProperties } from './organizationProperties';
|
||||
|
||||
interface IGetMembersParameters {
|
||||
org: string;
|
||||
|
@ -208,8 +209,8 @@ export class Organization {
|
|||
private _nativeManagementUrl: string;
|
||||
|
||||
private _operations: IOperationsInstance;
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _usesGitHubApp: boolean;
|
||||
private _settings: OrganizationSetting;
|
||||
|
||||
|
@ -221,6 +222,7 @@ export class Organization {
|
|||
private _domains: OrganizationDomains;
|
||||
|
||||
private _copilot: OrganizationCopilot;
|
||||
private _customProperties: OrganizationProperties;
|
||||
|
||||
id: number;
|
||||
uncontrolled: boolean;
|
||||
|
@ -229,8 +231,8 @@ export class Organization {
|
|||
operations: IOperationsInstance,
|
||||
name: string,
|
||||
settings: OrganizationSetting,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
public hasDynamicSettings: boolean
|
||||
) {
|
||||
this._name = settings.organizationName || name;
|
||||
|
@ -365,6 +367,17 @@ export class Organization {
|
|||
return this._copilot;
|
||||
}
|
||||
|
||||
get customProperties() {
|
||||
if (!this._customProperties) {
|
||||
this._customProperties = new OrganizationProperties(
|
||||
this,
|
||||
this._getSpecificAuthorizationHeader.bind(this),
|
||||
this._operations
|
||||
);
|
||||
}
|
||||
return this._customProperties;
|
||||
}
|
||||
|
||||
get domains() {
|
||||
if (!this._domains) {
|
||||
this._domains = new OrganizationDomains(
|
||||
|
@ -568,7 +581,11 @@ export class Organization {
|
|||
}
|
||||
|
||||
get broadAccessTeams(): number[] {
|
||||
return this.getSpecialTeam(SpecialTeam.Everyone, 'everyone membership');
|
||||
return this.getSystemTeam(SystemTeam.Everyone, 'everyone membership');
|
||||
}
|
||||
|
||||
get openAccessTeams(): number[] {
|
||||
return this.getSystemTeam(SystemTeam.OpenAccess, 'open access');
|
||||
}
|
||||
|
||||
get invitationTeam(): Team {
|
||||
|
@ -580,7 +597,7 @@ export class Organization {
|
|||
}
|
||||
|
||||
get systemSudoersTeam(): Team {
|
||||
const teams = this.getSpecialTeam(SpecialTeam.GlobalSudo, 'system sudoers');
|
||||
const teams = this.getSystemTeam(SystemTeam.GlobalSudo, 'system sudoers');
|
||||
if (teams.length > 1) {
|
||||
throw new Error('Multiple system sudoer teams are not supported.');
|
||||
}
|
||||
|
@ -592,7 +609,7 @@ export class Organization {
|
|||
}
|
||||
|
||||
get sudoersTeam(): Team {
|
||||
const teams = this.getSpecialTeam(SpecialTeam.Sudo, 'organization sudoers');
|
||||
const teams = this.getSystemTeam(SystemTeam.Sudo, 'organization sudoers');
|
||||
if (teams.length > 1) {
|
||||
throw new Error('Multiple sudoer teams are not supported.');
|
||||
}
|
||||
|
@ -606,15 +623,15 @@ export class Organization {
|
|||
return this._settings;
|
||||
}
|
||||
|
||||
get specialRepositoryPermissionTeams() {
|
||||
get specialSystemTeams() {
|
||||
return {
|
||||
read: this.getSpecialTeam(SpecialTeam.SystemRead, 'read everything'),
|
||||
write: this.getSpecialTeam(SpecialTeam.SystemWrite, 'write everything'),
|
||||
admin: this.getSpecialTeam(SpecialTeam.SystemAdmin, 'administer everything'),
|
||||
read: this.getSystemTeam(SystemTeam.SystemRead, 'read everything'),
|
||||
write: this.getSystemTeam(SystemTeam.SystemWrite, 'write everything'),
|
||||
admin: this.getSystemTeam(SystemTeam.SystemAdmin, 'administer everything'),
|
||||
};
|
||||
}
|
||||
|
||||
getAuthorizationHeader(): IPurposefulGetAuthorizationHeader {
|
||||
getAuthorizationHeader(): PurposefulGetAuthorizationHeader {
|
||||
return this._getAuthorizationHeader;
|
||||
}
|
||||
|
||||
|
@ -688,7 +705,13 @@ export class Organization {
|
|||
teamIds.push(broadAccessTeams[i]); // is the actual ID, not the team object
|
||||
}
|
||||
}
|
||||
const specialTeams = this.specialRepositoryPermissionTeams;
|
||||
const openAccessTeams = this.openAccessTeams;
|
||||
if (openAccessTeams) {
|
||||
for (let i = 0; i < openAccessTeams.length; i++) {
|
||||
teamIds.push(openAccessTeams[i]); // is the actual ID, not the team object
|
||||
}
|
||||
}
|
||||
const specialTeams = this.specialSystemTeams;
|
||||
const keys = Object.getOwnPropertyNames(specialTeams);
|
||||
keys.forEach((type) => {
|
||||
const values = specialTeams[type];
|
||||
|
@ -721,12 +744,12 @@ export class Organization {
|
|||
);
|
||||
}
|
||||
|
||||
async getRepositoryCreateGitHubToken(): Promise<IAuthorizationHeaderValue> {
|
||||
async getRepositoryCreateGitHubToken(): Promise<AuthorizationHeaderValue> {
|
||||
// This method leaks/releases the owner token. In the future a more crisp
|
||||
// way of accomplishing this without exposing the token should be created.
|
||||
// The function name is specific to the intended use instead of a general-
|
||||
// purpose token name.
|
||||
const token = await (this.authorize(AppPurpose.Operations) as IGetAuthorizationHeader)();
|
||||
const token = await (this.authorize(AppPurpose.Operations) as GetAuthorizationHeader)();
|
||||
token.source = 'repository create token';
|
||||
return token;
|
||||
}
|
||||
|
@ -1190,7 +1213,7 @@ export class Organization {
|
|||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
AppPurpose.Data
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
const github = operations.github;
|
||||
const parameters: IGetMembersParameters = {
|
||||
org: this.name,
|
||||
|
@ -1299,7 +1322,7 @@ export class Organization {
|
|||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
AppPurpose.Data
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
const teamEntities = await github.collections.getOrgTeams(getAuthorizationHeader, parameters, caching);
|
||||
const teams = common.createInstances<Team>(this, this.teamFromEntity, teamEntities);
|
||||
return teams;
|
||||
|
@ -1325,12 +1348,12 @@ export class Organization {
|
|||
if (queryCache?.supportsOrganizationMembership) {
|
||||
try {
|
||||
if (!optionalId) {
|
||||
const centralOps = operationsWithCapability<IOperationsCentralOperationsToken>(
|
||||
const ops = operationsWithCapability<IOperationsCentralOperationsToken>(
|
||||
operations,
|
||||
CoreCapability.GitHubCentralOperations
|
||||
);
|
||||
if (centralOps) {
|
||||
const account = await centralOps.getAccountByUsername(login);
|
||||
if (ops) {
|
||||
const account = await ops.getAccountByUsername(login);
|
||||
optionalId = account.id.toString();
|
||||
}
|
||||
}
|
||||
|
@ -1362,19 +1385,16 @@ export class Organization {
|
|||
}
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose): IGetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose): GetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): IGetAuthorizationHeader {
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): GetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
|
@ -1516,23 +1536,15 @@ export class Organization {
|
|||
return { settings, operations };
|
||||
}
|
||||
|
||||
private getSpecialTeam(specialTeam: SpecialTeam, friendlyName: string, throwIfMissing?: boolean): number[] {
|
||||
let teamId: number = null;
|
||||
for (const entry of this._settings.specialTeams) {
|
||||
if (entry.specialTeam === specialTeam) {
|
||||
teamId = entry.teamId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (throwIfMissing) {
|
||||
private getSystemTeam(teamType: SystemTeam, friendlyName: string, throwIfMissing?: boolean): number[] {
|
||||
const allOrgSystemTeams = this._settings.specialTeams;
|
||||
const matchingSystemTeamTypes = allOrgSystemTeams.filter((t) => t.specialTeam === teamType);
|
||||
const teams: number[] = matchingSystemTeamTypes.map((t) => t.teamId);
|
||||
if (throwIfMissing && teams.length === 0) {
|
||||
throw new Error(
|
||||
`Missing configured organization "${this.name}" special team ${specialTeam} - ${friendlyName}`
|
||||
`Missing configured organization "${this.name}" special team ${teamType} - ${friendlyName}`
|
||||
);
|
||||
}
|
||||
const teams: number[] = [];
|
||||
if (teamId) {
|
||||
teams.push(teamId);
|
||||
}
|
||||
return teams;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
//
|
||||
|
||||
import {
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
IOperationsInstance,
|
||||
IPagedCacheOptions,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
throwIfNotGitHubCapable,
|
||||
} from '../interfaces';
|
||||
import type { CollectionCopilotSeatsOptions } from '../lib/github/collections';
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import { CacheDefault, getMaxAgeSeconds, getPageSize } from './operations/core';
|
||||
import { Organization } from './organization';
|
||||
|
||||
|
@ -30,7 +30,7 @@ export type CopilotSeatData = {
|
|||
export class OrganizationCopilot {
|
||||
constructor(
|
||||
private organization: Organization,
|
||||
private getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
private getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
private operations: IOperationsInstance
|
||||
) {}
|
||||
|
||||
|
@ -43,7 +43,7 @@ export class OrganizationCopilot {
|
|||
const getAuthorizationHeader = this.getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
appPurpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
const github = operations.github;
|
||||
const parameters: CollectionCopilotSeatsOptions = {
|
||||
org: this.organization.name,
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
//
|
||||
// Copyright (c) Microsoft.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import {
|
||||
ICacheOptions,
|
||||
ICacheOptionsWithPurpose,
|
||||
GetAuthorizationHeader,
|
||||
IOperationsInstance,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
PagedCacheOptionsWithPurpose,
|
||||
throwIfNotGitHubCapable,
|
||||
} from '../interfaces';
|
||||
import { HttpMethod } from '../lib/github';
|
||||
import { CreateError } from '../transitional';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import {
|
||||
CacheDefault,
|
||||
createCacheOptions,
|
||||
createPagedCacheOptions,
|
||||
getMaxAgeSeconds,
|
||||
getPageSize,
|
||||
popPurpose,
|
||||
symbolizeApiResponse,
|
||||
} from './operations/core';
|
||||
import { Organization } from './organization';
|
||||
|
||||
export enum CustomPropertyValueType {
|
||||
String = 'string',
|
||||
SingleSelect = 'single_select',
|
||||
}
|
||||
|
||||
export type OrganizationCustomPropertyEntity = {
|
||||
property_name: string;
|
||||
value_type: CustomPropertyValueType;
|
||||
required: boolean;
|
||||
description?: string;
|
||||
default_value?: string;
|
||||
allowed_values?: string[];
|
||||
};
|
||||
|
||||
type SetPropertyValue = {
|
||||
property_name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type CreateOrUpdateResponse = {
|
||||
properties: OrganizationCustomPropertyEntity[];
|
||||
};
|
||||
|
||||
export class OrganizationProperties {
|
||||
private _defaultPurpose = AppPurpose.Data;
|
||||
|
||||
constructor(
|
||||
private organization: Organization,
|
||||
private getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
private operations: IOperationsInstance
|
||||
) {}
|
||||
|
||||
private authorize(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this.getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
async getRepositoryCustomProperties(
|
||||
repositoryName: string,
|
||||
options?: ICacheOptionsWithPurpose
|
||||
): Promise<Record<string, string>> {
|
||||
options = options || {};
|
||||
const operations = throwIfNotGitHubCapable(this.operations);
|
||||
const { github } = operations;
|
||||
const purpose = popPurpose(options, this._defaultPurpose);
|
||||
const parameters = {
|
||||
owner: this.organization.name,
|
||||
repo: repositoryName,
|
||||
};
|
||||
const cacheOptions = createCacheOptions(operations, options);
|
||||
try {
|
||||
const responseArray = await github.request(
|
||||
this.authorize(purpose),
|
||||
'GET /repos/:owner/:repo/properties/values',
|
||||
parameters,
|
||||
cacheOptions
|
||||
);
|
||||
return symbolizeApiResponse(arrayToSetProperties(responseArray));
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getCustomProperties(
|
||||
options?: PagedCacheOptionsWithPurpose
|
||||
): Promise<OrganizationCustomPropertyEntity[]> {
|
||||
options = options || {};
|
||||
const operations = throwIfNotGitHubCapable(this.operations);
|
||||
const { github } = operations;
|
||||
const purpose = popPurpose(options, this._defaultPurpose);
|
||||
const parameters = {
|
||||
org: this.organization.name,
|
||||
per_page: getPageSize(operations),
|
||||
};
|
||||
const cacheOptions = createPagedCacheOptions(operations, options);
|
||||
try {
|
||||
const entities = await github.collections.collectAllPagesViaHttpGet<
|
||||
any,
|
||||
OrganizationCustomPropertyEntity
|
||||
>(
|
||||
this.authorize(purpose),
|
||||
'orgCustomProps',
|
||||
'GET /orgs/:org/properties/schema',
|
||||
parameters,
|
||||
cacheOptions
|
||||
);
|
||||
return symbolizeApiResponse(entities);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getCustomProperty(
|
||||
propertyName: string,
|
||||
options?: ICacheOptionsWithPurpose
|
||||
): Promise<OrganizationCustomPropertyEntity> {
|
||||
options = options || {};
|
||||
const operations = throwIfNotGitHubCapable(this.operations);
|
||||
const { github } = operations;
|
||||
if (!propertyName) {
|
||||
throw CreateError.InvalidParameters('propertyName');
|
||||
}
|
||||
const purpose = popPurpose(options, this._defaultPurpose);
|
||||
const parameters = {
|
||||
org: this.organization.name,
|
||||
custom_property_name: propertyName,
|
||||
};
|
||||
const cacheOptions: ICacheOptions = {
|
||||
maxAgeSeconds: getMaxAgeSeconds(operations, CacheDefault.accountDetailStaleSeconds, options, 60),
|
||||
};
|
||||
if (options.backgroundRefresh !== undefined) {
|
||||
cacheOptions.backgroundRefresh = options.backgroundRefresh;
|
||||
}
|
||||
try {
|
||||
const entity = (await github.request(
|
||||
this.authorize(purpose),
|
||||
'GET /orgs/:org/properties/schema/:custom_property_name',
|
||||
parameters,
|
||||
cacheOptions
|
||||
)) as OrganizationCustomPropertyEntity;
|
||||
return symbolizeApiResponse(entity);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteProperty(propertyName: string, purpose?: AppPurposeTypes): Promise<void> {
|
||||
const operations = throwIfNotGitHubCapable(this.operations);
|
||||
const parameters = {
|
||||
org: this.organization.name,
|
||||
custom_property_name: propertyName,
|
||||
};
|
||||
await operations.github.restApi(
|
||||
this.authorize(purpose || this._defaultPurpose),
|
||||
HttpMethod.Delete,
|
||||
'/orgs/:org/properties/schema/:custom_property_name',
|
||||
parameters
|
||||
);
|
||||
}
|
||||
|
||||
async createOrUpdate(
|
||||
properties: OrganizationCustomPropertyEntity[],
|
||||
purpose?: AppPurposeTypes
|
||||
): Promise<OrganizationCustomPropertyEntity[]> {
|
||||
const operations = throwIfNotGitHubCapable(this.operations);
|
||||
const parameters = {
|
||||
org: this.organization.name,
|
||||
properties,
|
||||
};
|
||||
const res = (await operations.github.restApi(
|
||||
this.authorize(purpose || this._defaultPurpose),
|
||||
HttpMethod.Patch,
|
||||
'/orgs/:org/properties/schema',
|
||||
parameters
|
||||
)) as CreateOrUpdateResponse;
|
||||
return res.properties;
|
||||
}
|
||||
|
||||
createOrUpdateRepositoryProperties(
|
||||
repositoryName: string,
|
||||
propertiesAndValues: Record<string, string>,
|
||||
purpose?: AppPurposeTypes
|
||||
): Promise<void> {
|
||||
const names = [repositoryName];
|
||||
return this.createOrUpdateRepositoriesProperties(names, propertiesAndValues, purpose);
|
||||
}
|
||||
|
||||
async createOrUpdateRepositoriesProperties(
|
||||
organizationRepositoryNames: string[],
|
||||
propertiesAndValues: Record<string, string>,
|
||||
purpose?: AppPurposeTypes
|
||||
): Promise<void> {
|
||||
const operations = throwIfNotGitHubCapable(this.operations);
|
||||
if (organizationRepositoryNames.length > 30) {
|
||||
throw CreateError.InvalidParameters(
|
||||
'GitHub has a hard limit of 30 repositories to update in a single patch'
|
||||
);
|
||||
}
|
||||
const parameters = {
|
||||
repository_names: organizationRepositoryNames,
|
||||
org: this.organization.name,
|
||||
properties: setPropertiesRecordToArray(propertiesAndValues),
|
||||
};
|
||||
(await operations.github.restApi(
|
||||
this.authorize(purpose || this._defaultPurpose),
|
||||
HttpMethod.Patch,
|
||||
'/orgs/:org/properties/values',
|
||||
parameters
|
||||
)) as CreateOrUpdateResponse;
|
||||
}
|
||||
}
|
||||
|
||||
function setPropertiesRecordToArray(propertiesAndValues: Record<string, string>) {
|
||||
const keys = Object.getOwnPropertyNames(propertiesAndValues);
|
||||
const properties: SetPropertyValue[] = [];
|
||||
for (const key of keys) {
|
||||
properties.push({
|
||||
property_name: key,
|
||||
value: propertiesAndValues[key],
|
||||
});
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
function arrayToSetProperties(properties: SetPropertyValue[]) {
|
||||
const propertiesAndValues: Record<string, string> = {};
|
||||
for (const property of properties) {
|
||||
propertiesAndValues[property.property_name] = property.value;
|
||||
}
|
||||
return propertiesAndValues;
|
||||
}
|
|
@ -3,13 +3,13 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import { Organization, Repository } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
throwIfNotGitHubCapable,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
import {
|
||||
decorateIterable,
|
||||
|
@ -117,8 +117,8 @@ export class OrganizationProject {
|
|||
private _projects: OrganizationProjects;
|
||||
private _operations: IOperationsInstance;
|
||||
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _purpose: AppPurpose;
|
||||
|
||||
private _id: string;
|
||||
|
@ -126,8 +126,8 @@ export class OrganizationProject {
|
|||
constructor(
|
||||
organizationProjects: OrganizationProjects,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
projectId: string
|
||||
) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
|
@ -413,19 +413,16 @@ export class OrganizationProject {
|
|||
}
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose = this._purpose): IGetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose = this._purpose): GetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import { Organization } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
throwIfNotGitHubCapable,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
import {
|
||||
decorateIterable,
|
||||
|
@ -30,8 +30,8 @@ export class OrganizationProjectView {
|
|||
private _project: OrganizationProject;
|
||||
private _operations: IOperationsInstance;
|
||||
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _purpose: AppPurpose;
|
||||
|
||||
private _id: string;
|
||||
|
@ -41,8 +41,8 @@ export class OrganizationProjectView {
|
|||
constructor(
|
||||
organizationProject: OrganizationProject,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
projectId: string,
|
||||
essentials?: ProjectViewEssentials
|
||||
) {
|
||||
|
@ -217,19 +217,16 @@ export class OrganizationProjectView {
|
|||
// }
|
||||
// }
|
||||
|
||||
private authorize(purpose: AppPurpose = this._purpose): IGetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose = this._purpose): GetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import { Organization } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
throwIfNotGitHubCapable,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
import {
|
||||
decorateIterable,
|
||||
|
@ -35,15 +35,15 @@ export class OrganizationProjects {
|
|||
private _organization: Organization;
|
||||
private _operations: IOperationsInstance;
|
||||
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _purpose: AppPurpose;
|
||||
|
||||
constructor(
|
||||
organization: Organization,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader
|
||||
) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
this._getSpecificAuthorizationHeader = getSpecificAuthorizationHeader;
|
||||
|
@ -134,19 +134,16 @@ export class OrganizationProjects {
|
|||
}
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose = this._purpose): IGetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose = this._purpose): GetAuthorizationHeader {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ import {
|
|||
RepositoryIssue,
|
||||
} from '.';
|
||||
import { RepositoryMetadataEntity } from '../entities/repositoryMetadata/repositoryMetadata';
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import {
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
IOperationsInstance,
|
||||
ICacheOptions,
|
||||
throwIfNotGitHubCapable,
|
||||
|
@ -42,7 +42,7 @@ import {
|
|||
IGitHubSecretScanningAlert,
|
||||
operationsWithCapability,
|
||||
IOperationsServiceAccounts,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
IRepositoryGetIssuesOptions,
|
||||
IOperationsRepositoryMetadataProvider,
|
||||
IOperationsUrls,
|
||||
|
@ -214,8 +214,8 @@ export class Repository {
|
|||
|
||||
private _awesomeness: number;
|
||||
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _operations: IOperationsInstance;
|
||||
|
||||
private _organization: Organization;
|
||||
|
@ -366,8 +366,8 @@ export class Repository {
|
|||
constructor(
|
||||
organization: Organization,
|
||||
entity: any,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
operations: IOperationsInstance
|
||||
) {
|
||||
this._organization = organization;
|
||||
|
@ -884,9 +884,8 @@ export class Repository {
|
|||
cacheOptions.backgroundRefresh = options.backgroundRefresh;
|
||||
}
|
||||
try {
|
||||
// CONSIDER: need a fallback authentication approach: try and app for a specific capability (the installation knows) and fallback to central ops
|
||||
// const centralOps = operationsWithCapability<IOperationsCentralOperationsToken>(operations, CoreCapability.GitHubCentralOperations);
|
||||
const tokenSource = this._getSpecificAuthorizationHeader(AppPurpose.Data); // centralOps ? centralOps.getCentralOperationsToken()(AppPurpose.Data) :
|
||||
// CONSIDER: need a fallback authentication approach: try and app for a specific capability
|
||||
const tokenSource = this._getSpecificAuthorizationHeader(AppPurpose.Data);
|
||||
const token = await tokenSource;
|
||||
return await operations.github.call(token, 'repos.getPages', parameters, cacheOptions);
|
||||
} catch (error) {
|
||||
|
@ -1365,9 +1364,6 @@ export class Repository {
|
|||
private: options.private,
|
||||
}
|
||||
);
|
||||
// BUG: GitHub Apps do not work with locking down no repository permissions as documented here: https://github.community/t5/GitHub-API-Development-and/GitHub-App-cannot-patch-repo-visibility-in-org-with-repo/m-p/33448#M3150
|
||||
// const token = this._operations.getCentralOperationsToken();
|
||||
// return this._operations.github.post(token, 'repos.update', parameters);
|
||||
return operations.github.post(this.authorize(AppPurpose.Operations), 'repos.update', parameters);
|
||||
}
|
||||
|
||||
|
@ -1589,7 +1585,10 @@ export class Repository {
|
|||
const teams = (await this.getTeamPermissions()).filter((tp) => tp.permission === 'admin');
|
||||
for (let i = 0; i < teams.length; i++) {
|
||||
const team = teams[i];
|
||||
if (excludeBroadAndSystemTeams && (team.team.isSystemTeam || team.team.isBroadAccessTeam)) {
|
||||
if (
|
||||
excludeBroadAndSystemTeams &&
|
||||
(team.team.isSystemTeam || team.team.isBroadAccessTeam || team.team.isOpenAccessTeam)
|
||||
) {
|
||||
// Do not include broad access teams
|
||||
continue;
|
||||
}
|
||||
|
@ -1699,19 +1698,16 @@ export class Repository {
|
|||
return Array.from(users.values());
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
private specificAuthorization(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
private specificAuthorization(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getSpecificHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getSpecificHeader;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
import { Repository } from './repository';
|
||||
import { getPageSize, getMaxAgeSeconds, CacheDefault } from '.';
|
||||
import { AppPurpose } from './githubApps';
|
||||
import { AppPurpose } from '../lib/github/appPurposes';
|
||||
import {
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
IOperationsInstance,
|
||||
throwIfNotGitHubCapable,
|
||||
ICacheOptions,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
|
||||
export interface IGitHubActionWorkflowsResponse {
|
||||
|
@ -33,16 +33,16 @@ export interface IGitHubActionWorkflow {
|
|||
}
|
||||
|
||||
export class RepositoryActions {
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _operations: IOperationsInstance;
|
||||
|
||||
private _repository: Repository;
|
||||
|
||||
constructor(
|
||||
repository: Repository,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
operations: IOperationsInstance
|
||||
) {
|
||||
this._repository = repository;
|
||||
|
@ -175,11 +175,11 @@ export class RepositoryActions {
|
|||
return entity;
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose): IGetAuthorizationHeader | string {
|
||||
private authorize(purpose: AppPurpose): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
|
||||
import { Repository } from './repository';
|
||||
import { wrapError } from '../utils';
|
||||
import { AppPurpose } from './githubApps';
|
||||
import { AppPurpose } from '../lib/github/appPurposes';
|
||||
import { CacheDefault, getMaxAgeSeconds, Operations } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
GitHubIssueState,
|
||||
IIssueLabel,
|
||||
throwIfNotGitHubCapable,
|
||||
ICacheOptions,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
GitHubIssuePatchParameters,
|
||||
GitHubStateReason,
|
||||
} from '../interfaces';
|
||||
|
@ -22,7 +22,7 @@ import { CreateError, ErrorHelper } from '../transitional';
|
|||
|
||||
export class RepositoryIssue {
|
||||
private _operations: IOperationsInstance;
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
|
||||
private _number: number;
|
||||
private _repository: Repository;
|
||||
|
@ -33,7 +33,7 @@ export class RepositoryIssue {
|
|||
repository: Repository,
|
||||
issueNumber: number,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
entity?: any
|
||||
) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
|
@ -211,11 +211,8 @@ export class RepositoryIssue {
|
|||
return false;
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose): IGetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
|
||||
import { Repository } from './repository';
|
||||
import { wrapError } from '../utils';
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import { CacheDefault, getMaxAgeSeconds } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
GitHubIssueState,
|
||||
throwIfNotGitHubCapable,
|
||||
ICacheOptions,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
ICacheOptionsWithPurpose,
|
||||
} from '../interfaces';
|
||||
import { ErrorHelper } from '../transitional';
|
||||
|
@ -22,8 +22,8 @@ import * as common from './common';
|
|||
|
||||
export class RepositoryProject {
|
||||
private _operations: IOperationsInstance;
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
|
||||
private _id: number;
|
||||
private _repository: Repository;
|
||||
|
@ -36,8 +36,8 @@ export class RepositoryProject {
|
|||
repository: Repository,
|
||||
projectId: number,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
entity?: any
|
||||
) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
|
@ -213,11 +213,11 @@ export class RepositoryProject {
|
|||
return false;
|
||||
}
|
||||
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,13 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { AppPurpose } from './githubApps';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
IGetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
import { AppPurpose } from '../lib/github/appPurposes';
|
||||
import { IOperationsInstance, PurposefulGetAuthorizationHeader, GetAuthorizationHeader } from '../interfaces';
|
||||
import { RepositoryProjectColumn } from './repositoryProjectColumn';
|
||||
|
||||
export class RepositoryProjectCard {
|
||||
private _operations: IOperationsInstance;
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
|
||||
private _id: number;
|
||||
private _column: RepositoryProjectColumn;
|
||||
|
@ -24,7 +20,7 @@ export class RepositoryProjectCard {
|
|||
column: RepositoryProjectColumn,
|
||||
cardId: number,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
entity?: any
|
||||
) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
|
@ -64,11 +60,8 @@ export class RepositoryProjectCard {
|
|||
return this._column;
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose): IGetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
//
|
||||
|
||||
import { CacheDefault, getMaxAgeSeconds, RepositoryIssue } from '.';
|
||||
import { AppPurpose, AppPurposeTypes } from './githubApps';
|
||||
import { AppPurpose, AppPurposeTypes } from '../lib/github/appPurposes';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
IGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
ICacheOptionsWithPurpose,
|
||||
throwIfNotGitHubCapable,
|
||||
ICacheOptions,
|
||||
|
@ -20,7 +20,7 @@ import { CreateError } from '../transitional';
|
|||
|
||||
export class RepositoryProjectColumn {
|
||||
private _operations: IOperationsInstance;
|
||||
private _getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
|
||||
private _id: number;
|
||||
private _project: RepositoryProject;
|
||||
|
@ -31,7 +31,7 @@ export class RepositoryProjectColumn {
|
|||
project: RepositoryProject,
|
||||
columnId: number,
|
||||
operations: IOperationsInstance,
|
||||
getSpecificAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getSpecificAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
entity?: any
|
||||
) {
|
||||
this._getSpecificAuthorizationHeader = getSpecificAuthorizationHeader;
|
||||
|
@ -136,11 +136,11 @@ export class RepositoryProjectColumn {
|
|||
return card;
|
||||
}
|
||||
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): IGetAuthorizationHeader | string {
|
||||
private authorizeSpecificPurpose(purpose: AppPurposeTypes): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getSpecificAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
|
||||
import { Repository } from './repository';
|
||||
import { wrapError } from '../utils';
|
||||
import { AppPurpose } from './githubApps';
|
||||
import { AppPurpose } from '../lib/github/appPurposes';
|
||||
import { CacheDefault, getMaxAgeSeconds } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
GitHubIssueState,
|
||||
IIssueLabel,
|
||||
throwIfNotGitHubCapable,
|
||||
ICacheOptions,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
} from '../interfaces';
|
||||
import { ErrorHelper } from '../transitional';
|
||||
|
||||
|
@ -23,7 +23,7 @@ import { ErrorHelper } from '../transitional';
|
|||
|
||||
export class RepositoryPullRequest {
|
||||
private _operations: IOperationsInstance;
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
|
||||
private _number: number;
|
||||
private _repository: Repository;
|
||||
|
@ -34,7 +34,7 @@ export class RepositoryPullRequest {
|
|||
repository: Repository,
|
||||
pullRequestNumber: number,
|
||||
operations: IOperationsInstance,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
entity?: any
|
||||
) {
|
||||
this._getAuthorizationHeader = getAuthorizationHeader;
|
||||
|
@ -219,11 +219,8 @@ export class RepositoryPullRequest {
|
|||
return false;
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose): IGetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ import { TeamMember } from './teamMember';
|
|||
import { TeamRepositoryPermission } from './teamRepositoryPermission';
|
||||
import { IApprovalProvider } from '../entities/teamJoinApproval/approvalProvider';
|
||||
import { TeamJoinApprovalEntity } from '../entities/teamJoinApproval/teamJoinApproval';
|
||||
import { AppPurpose } from './githubApps';
|
||||
import { AppPurpose } from '../lib/github/appPurposes';
|
||||
import { CacheDefault, getMaxAgeSeconds, getPageSize, Organization } from '.';
|
||||
import {
|
||||
IOperationsInstance,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
TeamJsonFormat,
|
||||
throwIfNotCapable,
|
||||
IOperationsUrls,
|
||||
|
@ -25,7 +25,7 @@ import {
|
|||
ICacheOptions,
|
||||
throwIfNotGitHubCapable,
|
||||
IPagedCacheOptions,
|
||||
IGetAuthorizationHeader,
|
||||
GetAuthorizationHeader,
|
||||
IUpdateTeamMembershipOptions,
|
||||
GitHubTeamRole,
|
||||
ITeamMembershipRoleState,
|
||||
|
@ -82,7 +82,7 @@ export class Team {
|
|||
|
||||
private _organization: Organization;
|
||||
private _operations: IOperationsInstance;
|
||||
private _getAuthorizationHeader: IPurposefulGetAuthorizationHeader;
|
||||
private _getAuthorizationHeader: PurposefulGetAuthorizationHeader;
|
||||
|
||||
private _id: number;
|
||||
|
||||
|
@ -139,7 +139,7 @@ export class Team {
|
|||
constructor(
|
||||
organization: Organization,
|
||||
entity,
|
||||
getAuthorizationHeader: IPurposefulGetAuthorizationHeader,
|
||||
getAuthorizationHeader: PurposefulGetAuthorizationHeader,
|
||||
operations: IOperationsInstance
|
||||
) {
|
||||
if (!entity || !entity.id) {
|
||||
|
@ -181,6 +181,7 @@ export class Team {
|
|||
clone.corporateMetadata = {
|
||||
isSystemTeam: this.isSystemTeam,
|
||||
isBroadAccessTeam: this.isBroadAccessTeam,
|
||||
isOpenAccessTeam: this.isOpenAccessTeam,
|
||||
};
|
||||
return clone;
|
||||
}
|
||||
|
@ -303,7 +304,7 @@ export class Team {
|
|||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
AppPurpose.Data
|
||||
) as IGetAuthorizationHeader;
|
||||
) as GetAuthorizationHeader;
|
||||
const teamEntities = await github.collections.getTeamChildTeams(
|
||||
getAuthorizationHeader,
|
||||
parameters,
|
||||
|
@ -323,6 +324,15 @@ export class Team {
|
|||
return res >= 0;
|
||||
}
|
||||
|
||||
get isOpenAccessTeam(): boolean {
|
||||
const teams = this._organization.openAccessTeams;
|
||||
if (typeof this._id !== 'number') {
|
||||
throw new Error('Team.id must be a number');
|
||||
}
|
||||
const res = teams.indexOf(this._id);
|
||||
return res >= 0;
|
||||
}
|
||||
|
||||
get isSystemTeam(): boolean {
|
||||
const systemTeams = this._organization.systemTeamIds;
|
||||
const res = systemTeams.indexOf(this._id);
|
||||
|
@ -687,11 +697,8 @@ export class Team {
|
|||
};
|
||||
}
|
||||
|
||||
private authorize(purpose: AppPurpose): IGetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(
|
||||
this,
|
||||
purpose
|
||||
) as IGetAuthorizationHeader;
|
||||
private authorize(purpose: AppPurpose): GetAuthorizationHeader | string {
|
||||
const getAuthorizationHeader = this._getAuthorizationHeader.bind(this, purpose) as GetAuthorizationHeader;
|
||||
return getAuthorizationHeader;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"centralOperationsToken": "env://GITHUB_CENTRAL_OPERATIONS_TOKEN",
|
||||
"publicAccessToken": "env://GITHUB_PUBLIC_OPERATIONS_TOKEN",
|
||||
"primaryOrganizationId": "env://GITHUB_PRIMARY_ORGANIZATION_ID?type=integer"
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export type ConfigGitHubOrganization = {
|
|||
teamAllReposRead: string; // | number
|
||||
teamAllReposWrite: string; // | number
|
||||
teamAllReposAdmin: string;
|
||||
teamOpenAccess: string;
|
||||
teamSudoers: string;
|
||||
templates: string[];
|
||||
onboarding: boolean;
|
||||
|
|
|
@ -85,7 +85,7 @@ Here is a short overview about the meanings of the different parameters:
|
|||
- **setupbycorporateusername** (string) - Username (from the corporate identity system) of the user who set up the organization
|
||||
- **setupbycorporateid** (string) - Unique identifier (from the corporate identity system) for the user who set up the organization
|
||||
- **setupbycorporatedisplayname** (string) - Display name (from the corporate identity system) for the user who set up the organization
|
||||
- **specialteams** (object{specialTeam: string, teamId: integer}) - Special team configuration for the organization supported values for `specialTeam` types are: `everyone, sudo, globalSudo, systemWrite, systemRead, systemAdmin`. The `teamId` is the GitHub team ID for the special team.
|
||||
- **specialteams** (object{specialTeam: string, teamId: integer}) - Special team configuration for the organization supported values for `specialTeam` types are: `everyone, sudo, globalSudo, systemWrite, systemRead, systemAdmin, openAccess`. The `teamId` is the GitHub team ID for the special team.
|
||||
|
||||
### Static settings
|
||||
|
||||
|
|
|
@ -29,8 +29,9 @@ export interface IBasicGitHubAppInstallation {
|
|||
appPurposeId?: string;
|
||||
}
|
||||
|
||||
export enum SpecialTeam {
|
||||
export enum SystemTeam {
|
||||
Everyone = 'everyone', // teamAllMembers
|
||||
OpenAccess = 'openAccess',
|
||||
Sudo = 'sudo', // teamSudoers
|
||||
GlobalSudo = 'globalSudo', // teamPortalSudoers
|
||||
SystemWrite = 'systemWrite', // teamAllReposWrite
|
||||
|
@ -56,7 +57,7 @@ export enum OrganizationProperty {
|
|||
}
|
||||
|
||||
export interface ISpecialTeam {
|
||||
specialTeam: SpecialTeam;
|
||||
specialTeam: SystemTeam;
|
||||
teamId: number;
|
||||
}
|
||||
|
||||
|
@ -158,6 +159,16 @@ export class OrganizationSetting implements IOrganizationSettingProperties {
|
|||
return this.#ownerToken;
|
||||
}
|
||||
|
||||
static CreateEmptyWithOldToken(token: string, notes: string, organizationId?: number) {
|
||||
const settings = new OrganizationSetting();
|
||||
settings.#ownerToken = token;
|
||||
if (organizationId) {
|
||||
settings.organizationId = organizationId;
|
||||
}
|
||||
settings.operationsNotes = notes;
|
||||
return settings;
|
||||
}
|
||||
|
||||
static CreateFromStaticSettings(staticSettings: ConfigGitHubOrganization): OrganizationSetting {
|
||||
const clone = { ...staticSettings };
|
||||
const settings = new OrganizationSetting();
|
||||
|
@ -260,20 +271,33 @@ export class OrganizationSetting implements IOrganizationSettingProperties {
|
|||
: [clone.teamAllMembers as any];
|
||||
for (const value of arr) {
|
||||
settings.specialTeams.push({
|
||||
specialTeam: SpecialTeam.Everyone,
|
||||
specialTeam: SystemTeam.Everyone,
|
||||
teamId: Number(value),
|
||||
});
|
||||
}
|
||||
}
|
||||
delete clone.teamAllMembers;
|
||||
|
||||
if (clone.teamOpenAccess) {
|
||||
const arr = Array.isArray(clone.teamOpenAccess)
|
||||
? (clone.teamOpenAccess as any[])
|
||||
: [clone.teamOpenAccess as any];
|
||||
for (const value of arr) {
|
||||
settings.specialTeams.push({
|
||||
specialTeam: SystemTeam.OpenAccess,
|
||||
teamId: Number(value),
|
||||
});
|
||||
}
|
||||
}
|
||||
delete clone.teamOpenAccess;
|
||||
|
||||
if (clone.teamAllReposRead) {
|
||||
const arr = Array.isArray(clone.teamAllReposRead)
|
||||
? (clone.teamAllReposRead as any[])
|
||||
: [clone.teamAllReposRead as any];
|
||||
for (const value of arr) {
|
||||
settings.specialTeams.push({
|
||||
specialTeam: SpecialTeam.SystemRead,
|
||||
specialTeam: SystemTeam.SystemRead,
|
||||
teamId: Number(value),
|
||||
});
|
||||
}
|
||||
|
@ -286,7 +310,7 @@ export class OrganizationSetting implements IOrganizationSettingProperties {
|
|||
: [clone.teamAllReposWrite as any];
|
||||
for (const value of arr) {
|
||||
settings.specialTeams.push({
|
||||
specialTeam: SpecialTeam.SystemWrite,
|
||||
specialTeam: SystemTeam.SystemWrite,
|
||||
teamId: Number(value),
|
||||
});
|
||||
}
|
||||
|
@ -299,7 +323,7 @@ export class OrganizationSetting implements IOrganizationSettingProperties {
|
|||
: [clone.teamAllReposAdmin as any];
|
||||
for (const value of arr) {
|
||||
settings.specialTeams.push({
|
||||
specialTeam: SpecialTeam.SystemAdmin,
|
||||
specialTeam: SystemTeam.SystemAdmin,
|
||||
teamId: Number(value),
|
||||
});
|
||||
}
|
||||
|
@ -312,7 +336,7 @@ export class OrganizationSetting implements IOrganizationSettingProperties {
|
|||
: [clone.teamSudoers as any];
|
||||
for (const value of arr) {
|
||||
settings.specialTeams.push({
|
||||
specialTeam: SpecialTeam.Sudo,
|
||||
specialTeam: SystemTeam.Sudo,
|
||||
teamId: Number(value),
|
||||
});
|
||||
}
|
||||
|
@ -325,7 +349,7 @@ export class OrganizationSetting implements IOrganizationSettingProperties {
|
|||
: [clone.teamPortalSudoers as any];
|
||||
for (const value of arr) {
|
||||
settings.specialTeams.push({
|
||||
specialTeam: SpecialTeam.GlobalSudo,
|
||||
specialTeam: SystemTeam.GlobalSudo,
|
||||
teamId: Number(value),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ export async function lockdownRepository(
|
|||
const organization = repository.organization;
|
||||
try {
|
||||
const specialPermittedTeams = new Set([
|
||||
...organization.specialRepositoryPermissionTeams.admin,
|
||||
...organization.specialRepositoryPermissionTeams.write,
|
||||
...organization.specialRepositoryPermissionTeams.read,
|
||||
...organization.specialSystemTeams.admin,
|
||||
...organization.specialSystemTeams.write,
|
||||
...organization.specialSystemTeams.read,
|
||||
]);
|
||||
const teamPermissions = await repository.getTeamPermissions();
|
||||
for (const tp of teamPermissions) {
|
||||
|
|
|
@ -3,7 +3,13 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { CoreCapability, ICacheDefaultTimes, IPurposefulGetAuthorizationHeader, ICacheOptions } from '.';
|
||||
import {
|
||||
CoreCapability,
|
||||
ICacheDefaultTimes,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
ICacheOptions,
|
||||
GetAuthorizationHeader,
|
||||
} from '.';
|
||||
import { IProviders, ICorporateLink, ICachedEmployeeInformation } from '..';
|
||||
import { IRepositoryMetadataProvider } from '../../entities/repositoryMetadata/repositoryMetadataProvider';
|
||||
import { RestLibrary } from '../../lib/github';
|
||||
|
@ -78,8 +84,9 @@ export interface IOperationsLockdownFeatureFlags {
|
|||
}
|
||||
|
||||
export interface IOperationsCentralOperationsToken {
|
||||
getCentralOperationsToken(): IPurposefulGetAuthorizationHeader; // IGetAuthorizationHeader ?;
|
||||
getAccountByUsername(username: string, options?: ICacheOptions): Promise<Account>;
|
||||
getPublicReadOnlyStaticToken(): GetAuthorizationHeader;
|
||||
getPublicAuthorizationToken(): GetAuthorizationHeader;
|
||||
}
|
||||
|
||||
export function operationsIsCapable<T>(
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
import { AppPurposeTypes } from '../../business/githubApps';
|
||||
import { AppPurposeTypes } from '../../lib/github/appPurposes';
|
||||
|
||||
export interface ICacheOptions {
|
||||
backgroundRefresh?: any | null | undefined;
|
||||
|
@ -14,25 +14,29 @@ export interface ICacheOptionsWithPurpose extends ICacheOptions {
|
|||
purpose?: AppPurposeTypes;
|
||||
}
|
||||
|
||||
export type WithOptionalPurpose = {
|
||||
purpose?: AppPurposeTypes;
|
||||
};
|
||||
|
||||
export interface IPagedCacheOptions extends ICacheOptions {
|
||||
pageRequestDelay?: number | null | undefined; // FUTURE: could be a function, too
|
||||
}
|
||||
|
||||
export interface IPurposefulGetAuthorizationHeader {
|
||||
(purpose: AppPurposeTypes): Promise<IAuthorizationHeaderValue>;
|
||||
}
|
||||
export type PagedCacheOptionsWithPurpose = IPagedCacheOptions & WithOptionalPurpose;
|
||||
|
||||
export interface IGetAuthorizationHeader {
|
||||
(): Promise<IAuthorizationHeaderValue>;
|
||||
}
|
||||
export type PurposefulGetAuthorizationHeader = (
|
||||
purpose: AppPurposeTypes
|
||||
) => Promise<AuthorizationHeaderValue>;
|
||||
|
||||
export interface IAuthorizationHeaderValue {
|
||||
export type GetAuthorizationHeader = () => Promise<AuthorizationHeaderValue>;
|
||||
|
||||
export type AuthorizationHeaderValue = {
|
||||
value: string;
|
||||
purpose: AppPurposeTypes;
|
||||
source?: string;
|
||||
installationId?: number;
|
||||
organizationName?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface ICacheDefaultTimes {
|
||||
orgReposStaleSeconds: number;
|
||||
|
@ -58,6 +62,7 @@ export interface ICacheDefaultTimes {
|
|||
teamDetailStaleSeconds: number;
|
||||
orgRepoWebhooksStaleSeconds: number;
|
||||
teamRepositoryPermissionStaleSeconds: number;
|
||||
defaultStaleSeconds: number;
|
||||
}
|
||||
|
||||
// These "core capabilities" were created when the GitHub operations
|
||||
|
|
|
@ -7,9 +7,10 @@ import { ExecutionEnvironment } from '../../interfaces';
|
|||
import { CreateError } from '../../transitional';
|
||||
|
||||
import Debug from 'debug';
|
||||
import GitHubApplication from '../application';
|
||||
import { Operations } from '../operations';
|
||||
import { GitHubTokenManager } from './tokenManager';
|
||||
import GitHubApplication from '../../business/application';
|
||||
import { Operations, OperationsCore } from '../../business';
|
||||
|
||||
const debug = Debug('github:tokens');
|
||||
|
||||
export enum AppPurpose {
|
||||
|
@ -167,6 +168,9 @@ const appPurposeToConfigurationName = {
|
|||
};
|
||||
|
||||
export function getAppPurposeId(purpose: AppPurposeTypes) {
|
||||
if (!purpose) {
|
||||
return 'n/a';
|
||||
}
|
||||
if ((purpose as ICustomAppPurpose).isCustomAppPurpose === true) {
|
||||
return (purpose as ICustomAppPurpose).id;
|
||||
}
|
||||
|
@ -266,6 +270,7 @@ export interface IGitHubAppConfiguration {
|
|||
}
|
||||
|
||||
export interface IGitHubAppsOptions {
|
||||
operations: OperationsCore;
|
||||
// app: IReposApplication;
|
||||
configurations: Map<AppPurposeTypes, IGitHubAppConfiguration>;
|
||||
executionEnvironment: ExecutionEnvironment;
|
|
@ -7,10 +7,11 @@ import { request } from '@octokit/request';
|
|||
import { createAppAuth, InstallationAccessTokenAuthentication } from '@octokit/auth-app';
|
||||
import { AppAuthentication, AuthInterface } from '@octokit/auth-app/dist-types/types';
|
||||
|
||||
import { AppPurposeTypes, ICustomAppPurpose } from '.';
|
||||
import { IAuthorizationHeaderValue } from '../../interfaces';
|
||||
import { AppPurposeTypes, ICustomAppPurpose } from './appPurposes';
|
||||
import { AuthorizationHeaderValue } from '../../interfaces';
|
||||
|
||||
import Debug from 'debug';
|
||||
import { CreateError } from '../../transitional';
|
||||
const debug = Debug('github:tokens');
|
||||
|
||||
interface IInstallationToken {
|
||||
|
@ -27,6 +28,45 @@ interface IInstallationToken {
|
|||
const InstallationTokenLifetimeMilliseconds = 1000 * 60 * 60;
|
||||
const ValidityOffsetAfterNowMilliseconds = 1000 * 120; // how long to require validity in the future
|
||||
|
||||
export enum GitHubTokenType {
|
||||
PersonalAccessToken = 'ghp',
|
||||
OAuthAccessToken = 'gho',
|
||||
UserToServerToken = 'ghu',
|
||||
ServerToServerToken = 'ghs',
|
||||
RefreshToken = 'ghr',
|
||||
FineGrainedPersonalAccessToken = 'github_pat',
|
||||
}
|
||||
|
||||
export const GitHubTokenTypes = [
|
||||
GitHubTokenType.PersonalAccessToken,
|
||||
GitHubTokenType.OAuthAccessToken,
|
||||
GitHubTokenType.UserToServerToken,
|
||||
GitHubTokenType.ServerToServerToken,
|
||||
GitHubTokenType.RefreshToken,
|
||||
GitHubTokenType.FineGrainedPersonalAccessToken,
|
||||
];
|
||||
|
||||
export function getGitHubTokenTypeFromValue(value: string | AuthorizationHeaderValue): GitHubTokenType {
|
||||
if (!value) {
|
||||
throw CreateError.ParameterRequired('value');
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
value = value.value;
|
||||
} else if (typeof value !== 'string') {
|
||||
throw CreateError.InvalidParameters('value must be a string or AuthorizationHeaderValue');
|
||||
}
|
||||
if (!value.startsWith('token ')) {
|
||||
throw CreateError.InvalidParameters('value must start with "token "');
|
||||
}
|
||||
const tokenValue = value.substr(6);
|
||||
for (const tokenType of GitHubTokenTypes) {
|
||||
if (tokenValue.startsWith(tokenType)) {
|
||||
return tokenType;
|
||||
}
|
||||
}
|
||||
throw CreateError.InvalidParameters('value does not appear to be a GitHub token');
|
||||
}
|
||||
|
||||
export class GitHubAppTokens {
|
||||
#privateKey: string;
|
||||
private _appId: number;
|
||||
|
@ -39,23 +79,25 @@ export class GitHubAppTokens {
|
|||
|
||||
static CreateFromBase64EncodedFileString(
|
||||
purpose: AppPurposeTypes,
|
||||
slug: string,
|
||||
friendlyName: string,
|
||||
applicationId: number,
|
||||
fileContents: string,
|
||||
baseUrl?: string
|
||||
): GitHubAppTokens {
|
||||
const keyContents = Buffer.from(fileContents, 'base64').toString('utf8').replace(/\r\n/g, '\n');
|
||||
return new GitHubAppTokens(purpose, friendlyName, applicationId, keyContents, baseUrl);
|
||||
return new GitHubAppTokens(purpose, slug, friendlyName, applicationId, keyContents, baseUrl);
|
||||
}
|
||||
|
||||
static CreateFromString(
|
||||
purpose: AppPurposeTypes,
|
||||
slug: string,
|
||||
friendlyName: string,
|
||||
applicationId: number,
|
||||
value: string,
|
||||
baseUrl?: string
|
||||
): GitHubAppTokens {
|
||||
return new GitHubAppTokens(purpose, friendlyName, applicationId, value, baseUrl);
|
||||
return new GitHubAppTokens(purpose, slug, friendlyName, applicationId, value, baseUrl);
|
||||
}
|
||||
|
||||
get appId() {
|
||||
|
@ -68,6 +110,7 @@ export class GitHubAppTokens {
|
|||
|
||||
constructor(
|
||||
purpose: AppPurposeTypes,
|
||||
public slug: string,
|
||||
public friendlyName: string,
|
||||
appId: number,
|
||||
privateKey: string,
|
||||
|
@ -114,7 +157,7 @@ export class GitHubAppTokens {
|
|||
async getInstallationToken(
|
||||
installationId: number,
|
||||
organizationName: string
|
||||
): Promise<IAuthorizationHeaderValue> {
|
||||
): Promise<AuthorizationHeaderValue> {
|
||||
const now = new Date();
|
||||
const requiredValidityPeriod = new Date(now.getTime() + ValidityOffsetAfterNowMilliseconds);
|
||||
const latestToken = this.getLatestValidToken(installationId, requiredValidityPeriod);
|
|
@ -13,7 +13,7 @@ import { IRestResponse, flattenData } from './core';
|
|||
import { CompositeApiContext, CompositeIntelligentEngine } from './composite';
|
||||
import { Collaborator } from '../../business/collaborator';
|
||||
import { Team } from '../../business/team';
|
||||
import { IPagedCacheOptions, IGetAuthorizationHeader, IDictionary } from '../../interfaces';
|
||||
import { IPagedCacheOptions, GetAuthorizationHeader, IDictionary } from '../../interfaces';
|
||||
import { RestLibrary } from '.';
|
||||
import { sleep } from '../../utils';
|
||||
import GitHubApplication from '../../business/application';
|
||||
|
@ -26,6 +26,8 @@ export interface IGetAppInstallationsParameters {
|
|||
|
||||
type WithPage<T> = T & { page?: number };
|
||||
|
||||
type WithOctokitRequest<T> = T & { octokitRequest?: string };
|
||||
|
||||
export type CollectionCopilotSeatsOptions = {
|
||||
org: string;
|
||||
per_page?: number;
|
||||
|
@ -151,8 +153,56 @@ export class RestCollections {
|
|||
this.githubCall = githubCall;
|
||||
}
|
||||
|
||||
collectAllPages<ParametersType = any, EntityType = any>(
|
||||
token: string | GetAuthorizationHeader,
|
||||
collectionCacheKey: string,
|
||||
octokitApiName: string,
|
||||
parameters: ParametersType,
|
||||
cacheOptions: IPagedCacheOptions,
|
||||
fieldNamesToKeep?: string[] | WithSubPropertyReducer,
|
||||
arrayReducePropertyName?: string
|
||||
): Promise<EntityType[]> {
|
||||
return this.generalizedCollectionWithFilter(
|
||||
collectionCacheKey,
|
||||
octokitApiName,
|
||||
fieldNamesToKeep,
|
||||
token,
|
||||
parameters,
|
||||
cacheOptions,
|
||||
arrayReducePropertyName
|
||||
);
|
||||
}
|
||||
|
||||
collectAllPagesViaHttpGet<ParametersType = any, EntityType = any>(
|
||||
token: string | GetAuthorizationHeader,
|
||||
collectionCacheKey: string,
|
||||
getRestUrl: string,
|
||||
parameters: ParametersType,
|
||||
cacheOptions: IPagedCacheOptions,
|
||||
fieldNamesToKeep?: string[] | WithSubPropertyReducer,
|
||||
arrayReducePropertyName?: string
|
||||
): Promise<EntityType[]> {
|
||||
const expandedOptions: WithOctokitRequest<ParametersType> = Object.assign(
|
||||
{
|
||||
octokitRequest: getRestUrl.startsWith('GET ') ? getRestUrl.substr(4) : getRestUrl,
|
||||
},
|
||||
parameters
|
||||
);
|
||||
return this.collectAllPages<ParametersType, EntityType>(
|
||||
token,
|
||||
collectionCacheKey,
|
||||
'request',
|
||||
expandedOptions,
|
||||
cacheOptions,
|
||||
fieldNamesToKeep,
|
||||
arrayReducePropertyName
|
||||
);
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
getOrgRepos(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -167,7 +217,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getOrgTeams(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -182,7 +232,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getTeamChildTeams(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -197,7 +247,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getUserActivity(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -212,7 +262,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getOrgMembers(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -227,7 +277,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getOrganizationCopilotSeats(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options: CollectionCopilotSeatsOptions,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -252,7 +302,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getAppInstallations(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
parameters: IGetAppInstallationsParameters,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -273,7 +323,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoIssues(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any[]> {
|
||||
|
@ -288,7 +338,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoProjects(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any[]> {
|
||||
|
@ -303,7 +353,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoTeams(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -318,7 +368,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoContributors(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -333,7 +383,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoCollaborators(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -348,7 +398,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoInvitations(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -363,7 +413,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoBranches(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -378,7 +428,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getRepoPullRequests(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options: IListPullsParameters,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -393,7 +443,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getTeamMembers(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -408,7 +458,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
getTeamRepos(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options,
|
||||
cacheOptions: IPagedCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -423,7 +473,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
private async getGithubCollection<OptionsType>(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
methodName: string,
|
||||
options: OptionsType,
|
||||
cacheOptions: IPagedCacheOptions,
|
||||
|
@ -515,7 +565,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
private async getFilteredGithubCollection<DataType, OptionsType>(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
methodName: string,
|
||||
options: OptionsType,
|
||||
cacheOptions: IPagedCacheOptions,
|
||||
|
@ -580,7 +630,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
private async getFilteredGithubCollectionWithMetadataAnalysis<DataType, OptionsType>(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
methodName: string,
|
||||
options: OptionsType,
|
||||
cacheOptions: IPagedCacheOptions,
|
||||
|
@ -632,7 +682,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
private generalizedCollectionMethod(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
apiName: string,
|
||||
method,
|
||||
options,
|
||||
|
@ -650,7 +700,7 @@ export class RestCollections {
|
|||
}
|
||||
|
||||
private getCollectionAndFilter<DataType, OptionsType>(
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options: OptionsType,
|
||||
cacheOptions: IPagedCacheOptions,
|
||||
githubClientMethod: string,
|
||||
|
@ -658,7 +708,7 @@ export class RestCollections {
|
|||
arrayReducePropertyName?: string
|
||||
) {
|
||||
const capturedThis = this;
|
||||
return function (token: string | IGetAuthorizationHeader, options: OptionsType) {
|
||||
return function (token: string | GetAuthorizationHeader, options: OptionsType) {
|
||||
return capturedThis.getFilteredGithubCollectionWithMetadataAnalysis<DataType, OptionsType>(
|
||||
token,
|
||||
githubClientMethod,
|
||||
|
@ -674,7 +724,7 @@ export class RestCollections {
|
|||
name: string,
|
||||
githubClientMethod: string,
|
||||
propertiesToKeep: string[],
|
||||
token: string | IGetAuthorizationHeader,
|
||||
token: string | GetAuthorizationHeader,
|
||||
options: OptionsType,
|
||||
cacheOptions: IPagedCacheOptions,
|
||||
arrayReducePropertyName?: string
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
IRestMetadata,
|
||||
IRestResponse,
|
||||
} from './core';
|
||||
import { IGetAuthorizationHeader } from '../../interfaces';
|
||||
import { GetAuthorizationHeader } from '../../interfaces';
|
||||
|
||||
import appPackage from '../../package.json';
|
||||
|
||||
|
@ -34,7 +34,7 @@ const acceleratedExpirationMinutes = 60; // 1 hour
|
|||
export class CompositeApiContext extends ApiContext {
|
||||
private _apiMethod: any;
|
||||
private _apiTypePrefix: string;
|
||||
private _token: string | IGetAuthorizationHeader;
|
||||
private _token: string | GetAuthorizationHeader;
|
||||
private _cacheValues: IApiContextCacheValues;
|
||||
private _redisKeys: IApiContextRedisKeys;
|
||||
|
||||
|
@ -74,11 +74,11 @@ export class CompositeApiContext extends ApiContext {
|
|||
return this._cacheValues;
|
||||
}
|
||||
|
||||
get token(): string | IGetAuthorizationHeader {
|
||||
get token(): string | GetAuthorizationHeader {
|
||||
return this._token;
|
||||
}
|
||||
|
||||
overrideToken(token: string | IGetAuthorizationHeader) {
|
||||
overrideToken(token: string | GetAuthorizationHeader) {
|
||||
this._token = token;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { randomUUID } from 'crypto';
|
|||
import moment from 'moment';
|
||||
|
||||
import { RestLibrary } from '.';
|
||||
import { IAuthorizationHeaderValue } from '../../interfaces';
|
||||
import { AuthorizationHeaderValue } from '../../interfaces';
|
||||
import { sleep } from '../../utils';
|
||||
|
||||
import cost from './cost';
|
||||
|
@ -99,7 +99,7 @@ export abstract class ApiContext {
|
|||
|
||||
libraryContext: RestLibrary;
|
||||
etag?: string;
|
||||
tokenSource: IAuthorizationHeaderValue;
|
||||
tokenSource: AuthorizationHeaderValue;
|
||||
|
||||
abstract get apiTypePrefix(): string;
|
||||
abstract get cacheValues(): IApiContextCacheValues;
|
||||
|
|
|
@ -11,10 +11,10 @@ import {
|
|||
ICacheOptions,
|
||||
IGetOrganizationMembersOptions,
|
||||
IPagedCrossOrganizationCacheOptions,
|
||||
IPurposefulGetAuthorizationHeader,
|
||||
PurposefulGetAuthorizationHeader,
|
||||
ITeamMembershipOptions,
|
||||
} from '../../interfaces';
|
||||
import { AppPurpose } from '../../business/githubApps';
|
||||
import { AppPurpose } from './appPurposes';
|
||||
|
||||
interface IOrganizationsResponse extends IRestResponse {
|
||||
orgs?: any;
|
||||
|
@ -42,7 +42,7 @@ export class CrossOrganizationCollator {
|
|||
}
|
||||
|
||||
async orgMembers(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options: IGetOrganizationMembersOptions,
|
||||
cacheOptions: ICacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -57,13 +57,13 @@ export class CrossOrganizationCollator {
|
|||
return flattenData(data);
|
||||
}
|
||||
|
||||
async teams(orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>, options, cacheOptions) {
|
||||
async teams(orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>, options, cacheOptions) {
|
||||
const allTeams = await this.getAllTeams(orgsAndTokens, options, cacheOptions);
|
||||
return flattenData(allTeams);
|
||||
}
|
||||
|
||||
async teamMembers(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options: ITeamMembershipOptions,
|
||||
cacheOptions: ICacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -90,7 +90,7 @@ export class CrossOrganizationCollator {
|
|||
}
|
||||
|
||||
async repos(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options,
|
||||
cacheOptions: ICacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -99,7 +99,7 @@ export class CrossOrganizationCollator {
|
|||
}
|
||||
|
||||
async repoCollaborators(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options,
|
||||
cacheOptions: ICacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -126,7 +126,7 @@ export class CrossOrganizationCollator {
|
|||
}
|
||||
|
||||
async repoTeams(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options,
|
||||
cacheOptions: ICacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -173,7 +173,7 @@ export class CrossOrganizationCollator {
|
|||
}
|
||||
|
||||
private async getCrossOrganizationMethod(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
apiName: string,
|
||||
methodName: string,
|
||||
options,
|
||||
|
@ -242,7 +242,7 @@ export class CrossOrganizationCollator {
|
|||
|
||||
private crossOrganizationCollection(
|
||||
capturedThis: CrossOrganizationCollator,
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options,
|
||||
cacheOptions: IPagedCrossOrganizationCacheOptions,
|
||||
innerKeyType,
|
||||
|
@ -303,7 +303,7 @@ export class CrossOrganizationCollator {
|
|||
const localOptions = Object.assign(localOptionsTarget, options);
|
||||
delete localOptions.maxAgeSeconds;
|
||||
delete localOptions.backgroundRefresh;
|
||||
const token = orgsAndTokens.get(orgName.toLowerCase()) as IPurposefulGetAuthorizationHeader;
|
||||
const token = orgsAndTokens.get(orgName.toLowerCase()) as PurposefulGetAuthorizationHeader;
|
||||
if (!token) {
|
||||
throw new Error(`No token available for the organization ${orgName}`);
|
||||
}
|
||||
|
@ -348,7 +348,7 @@ export class CrossOrganizationCollator {
|
|||
}
|
||||
|
||||
private async getAllTeams(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options,
|
||||
cacheOptions: IPagedCrossOrganizationCacheOptions
|
||||
): Promise<any> {
|
||||
|
@ -364,7 +364,7 @@ export class CrossOrganizationCollator {
|
|||
}
|
||||
|
||||
private async getAllRepos(
|
||||
orgsAndTokens: Map<string, IPurposefulGetAuthorizationHeader>,
|
||||
orgsAndTokens: Map<string, PurposefulGetAuthorizationHeader>,
|
||||
options,
|
||||
cacheOptions: IPagedCrossOrganizationCacheOptions
|
||||
): Promise<any> {
|
||||
|
|
|
@ -13,17 +13,26 @@ import { CompositeIntelligentEngine } from './composite';
|
|||
import { RestCollections } from './collections';
|
||||
import { CrossOrganizationCollator } from './crossOrganization';
|
||||
import { LinkMethods } from './links';
|
||||
import { IGetAuthorizationHeader, IAuthorizationHeaderValue } from '../../interfaces';
|
||||
import { GetAuthorizationHeader, AuthorizationHeaderValue } from '../../interfaces';
|
||||
import { ICacheHelper } from '../caching';
|
||||
import { ICustomAppPurpose } from '../../business/githubApps';
|
||||
import { ICustomAppPurpose } from './appPurposes';
|
||||
import { CreateError } from '../../transitional';
|
||||
|
||||
export enum CacheMode {
|
||||
ValidateCache = 'ValidateCache',
|
||||
BackgroundRefresh = 'BackgroundRefresh',
|
||||
}
|
||||
|
||||
export enum HttpMethod {
|
||||
Get = 'GET',
|
||||
Post = 'POST',
|
||||
Put = 'PUT',
|
||||
Patch = 'PATCH',
|
||||
Delete = 'DELETE',
|
||||
}
|
||||
|
||||
export interface IGitHubPostFunction {
|
||||
(awaitToken: IGetAuthorizationHeader, api: string, parameters: any): Promise<any>;
|
||||
(awaitToken: GetAuthorizationHeader, api: string, parameters: any): Promise<any>;
|
||||
}
|
||||
|
||||
export type OctokitGraphqlOptions = {
|
||||
|
@ -128,25 +137,27 @@ export class RestLibrary {
|
|||
hasNextPage?: (any) => boolean;
|
||||
|
||||
private async resolveAuthorizationHeader(
|
||||
authorizationHeader: IGetAuthorizationHeader | IAuthorizationHeaderValue | string
|
||||
): Promise<string | IAuthorizationHeaderValue> {
|
||||
authorizationHeader: GetAuthorizationHeader | AuthorizationHeaderValue | string
|
||||
): Promise<string | AuthorizationHeaderValue> {
|
||||
let authorizationValue = null;
|
||||
try {
|
||||
if (typeof authorizationHeader === 'string') {
|
||||
if (!authorizationHeader) {
|
||||
throw CreateError.InvalidParameters('No authorization header');
|
||||
} else if (typeof authorizationHeader === 'string') {
|
||||
authorizationValue = authorizationHeader as string;
|
||||
} else if (typeof authorizationHeader === 'function') {
|
||||
let asFunc = authorizationHeader as IGetAuthorizationHeader;
|
||||
let resolved = asFunc.call(null) as Promise<IAuthorizationHeaderValue | string>;
|
||||
let asFunc = authorizationHeader as GetAuthorizationHeader;
|
||||
let resolved = asFunc.call(null) as Promise<AuthorizationHeaderValue | string>;
|
||||
authorizationValue = await resolved;
|
||||
if (typeof resolved === 'function') {
|
||||
asFunc = resolved as IGetAuthorizationHeader;
|
||||
resolved = asFunc.call(null) as Promise<IAuthorizationHeaderValue | string>;
|
||||
asFunc = resolved as GetAuthorizationHeader;
|
||||
resolved = asFunc.call(null) as Promise<AuthorizationHeaderValue | string>;
|
||||
authorizationValue = await resolved;
|
||||
}
|
||||
} else if (authorizationHeader && authorizationHeader['value']) {
|
||||
authorizationValue = authorizationHeader as IAuthorizationHeaderValue;
|
||||
authorizationValue = authorizationHeader as AuthorizationHeaderValue;
|
||||
} else {
|
||||
throw new Error('Invalid resolveAuthorizationHeader');
|
||||
throw CreateError.InvalidParameters('Unknown resolveAuthorizationHeader type');
|
||||
}
|
||||
} catch (getTokenError) {
|
||||
console.dir(getTokenError);
|
||||
|
@ -156,7 +167,7 @@ export class RestLibrary {
|
|||
}
|
||||
|
||||
async call(
|
||||
awaitToken: IGetAuthorizationHeader | IAuthorizationHeaderValue | string,
|
||||
awaitToken: GetAuthorizationHeader | AuthorizationHeaderValue | string,
|
||||
api: string,
|
||||
options,
|
||||
cacheOptions = null
|
||||
|
@ -181,20 +192,32 @@ export class RestLibrary {
|
|||
return result;
|
||||
}
|
||||
|
||||
request(token, restEndpoint, parameters: any, cacheOptions): Promise<any> {
|
||||
request(token: GetAuthorizationHeader | string, restEndpoint, parameters: any, cacheOptions): Promise<any> {
|
||||
parameters = parameters || {};
|
||||
parameters['octokitRequest'] = restEndpoint;
|
||||
return this.call(token, 'request', parameters, cacheOptions);
|
||||
}
|
||||
|
||||
requestAsPost(token, restEndpoint, parameters: any): Promise<any> {
|
||||
requestAsPost(token: GetAuthorizationHeader | string, restEndpoint, parameters: any): Promise<any> {
|
||||
parameters = parameters || {};
|
||||
parameters['octokitRequest'] = restEndpoint;
|
||||
return this.post(token, 'request', parameters);
|
||||
}
|
||||
|
||||
restApi(
|
||||
token: GetAuthorizationHeader | string,
|
||||
httpMethod: HttpMethod,
|
||||
restEndpoint: string,
|
||||
parameters: any
|
||||
): Promise<any> {
|
||||
const requestUrlValue = `${httpMethod} ${restEndpoint}`;
|
||||
return httpMethod === HttpMethod.Get
|
||||
? this.request(token, requestUrlValue, parameters, {})
|
||||
: this.requestAsPost(token, requestUrlValue, parameters);
|
||||
}
|
||||
|
||||
graphql<T = any>(
|
||||
token,
|
||||
token: GetAuthorizationHeader | string,
|
||||
query: string,
|
||||
parameters: any,
|
||||
graphqlOptions: OctokitGraphqlOptions = {}
|
||||
|
@ -203,7 +226,7 @@ export class RestLibrary {
|
|||
}
|
||||
|
||||
graphqlIteration<T = any>(
|
||||
token,
|
||||
token: GetAuthorizationHeader | string,
|
||||
query: string,
|
||||
parameters: any,
|
||||
graphqlOptions: OctokitGraphqlOptions = {}
|
||||
|
@ -228,7 +251,7 @@ export class RestLibrary {
|
|||
return this.post(token, api, parameters);
|
||||
}
|
||||
|
||||
async post(awaitToken: IGetAuthorizationHeader | string, api: string, options: any): Promise<any> {
|
||||
async post(awaitToken: GetAuthorizationHeader | string, api: string, options: any): Promise<any> {
|
||||
const method = restApi.IntelligentGitHubEngine.findLibraryMethod(this.github, api);
|
||||
if (!options.headers) {
|
||||
options.headers = {};
|
||||
|
@ -239,14 +262,14 @@ export class RestLibrary {
|
|||
delete options.allowEmptyResponse;
|
||||
massageData = noDataMassage;
|
||||
}
|
||||
let diagnosticHeaderInformation: IAuthorizationHeaderValue = null;
|
||||
let diagnosticHeaderInformation: AuthorizationHeaderValue = null;
|
||||
if (!options.headers.authorization) {
|
||||
const value = await this.resolveAuthorizationHeader(awaitToken);
|
||||
if ((value as IAuthorizationHeaderValue)?.purpose) {
|
||||
diagnosticHeaderInformation = value as IAuthorizationHeaderValue;
|
||||
if ((value as AuthorizationHeaderValue)?.purpose) {
|
||||
diagnosticHeaderInformation = value as AuthorizationHeaderValue;
|
||||
}
|
||||
options.headers.authorization =
|
||||
typeof value === 'string' ? (value as string) : (value as IAuthorizationHeaderValue).value;
|
||||
typeof value === 'string' ? (value as string) : (value as AuthorizationHeaderValue).value;
|
||||
}
|
||||
const diagnostic: Record<string, any> = {};
|
||||
try {
|
||||
|
@ -283,6 +306,10 @@ export class RestLibrary {
|
|||
return finalized;
|
||||
} catch (error) {
|
||||
console.log(`API ${api} POST error: ${error.message}`);
|
||||
if (error?.message?.includes('Unexpected end of JSON input')) {
|
||||
console.log('Usually a unicorn and bad GitHub 500');
|
||||
console.dir(error);
|
||||
}
|
||||
if (
|
||||
error?.message?.includes('Resource not accessible by integration') ||
|
||||
error?.message?.includes('Not Found')
|
||||
|
|
|
@ -28,12 +28,12 @@ import { getEntityDefinitions, GitHubResponseType, ResponseBodyType } from './en
|
|||
import appPackage from '../../package.json';
|
||||
import { ErrorHelper } from '../../transitional';
|
||||
|
||||
import type { IGetAuthorizationHeader, IAuthorizationHeaderValue } from '../../interfaces';
|
||||
import type { GetAuthorizationHeader, AuthorizationHeaderValue } from '../../interfaces';
|
||||
import {
|
||||
type IGitHubAppConfiguration,
|
||||
getAppPurposeId,
|
||||
tryGetAppPurposeAppConfiguration,
|
||||
} from '../../business/githubApps';
|
||||
} from './appPurposes';
|
||||
|
||||
const appVersion = appPackage.version;
|
||||
|
||||
|
@ -446,7 +446,7 @@ export class GitHubApiContext extends ApiContext {
|
|||
private _apiMethod: any;
|
||||
private _redisKeys: IApiContextRedisKeys;
|
||||
private _cacheValues: IApiContextCacheValues;
|
||||
private _token: string | IGetAuthorizationHeader | IAuthorizationHeaderValue;
|
||||
private _token: string | GetAuthorizationHeader | AuthorizationHeaderValue;
|
||||
|
||||
public fakeLink?: IGitHubLink;
|
||||
|
||||
|
@ -472,7 +472,7 @@ export class GitHubApiContext extends ApiContext {
|
|||
};
|
||||
}
|
||||
|
||||
get token(): string | IGetAuthorizationHeader | IAuthorizationHeaderValue {
|
||||
get token(): string | GetAuthorizationHeader | AuthorizationHeaderValue {
|
||||
return this._token;
|
||||
}
|
||||
|
||||
|
@ -511,9 +511,9 @@ export class GitHubApiContext extends ApiContext {
|
|||
this.libraryContext = libraryContext;
|
||||
}
|
||||
|
||||
overrideToken(token: string | IGetAuthorizationHeader | IAuthorizationHeaderValue) {
|
||||
overrideToken(token: string | GetAuthorizationHeader | AuthorizationHeaderValue) {
|
||||
if (token && token['value']) {
|
||||
const asPair = token as IAuthorizationHeaderValue;
|
||||
const asPair = token as AuthorizationHeaderValue;
|
||||
this._token = asPair.value;
|
||||
this.tokenSource = asPair;
|
||||
} else if (typeof token === 'string') {
|
||||
|
|
|
@ -15,20 +15,20 @@ import {
|
|||
GitHubAppPurposes,
|
||||
AppPurposeTypes,
|
||||
getAppPurposeId,
|
||||
} from '.';
|
||||
} from './appPurposes';
|
||||
import { GitHubAppTokens } from './appTokens';
|
||||
import { IAuthorizationHeaderValue, NoCacheNoBackground } from '../../interfaces';
|
||||
import { AuthorizationHeaderValue, GetAuthorizationHeader, NoCacheNoBackground } from '../../interfaces';
|
||||
import { OrganizationSetting } from '../../entities/organizationSettings/organizationSetting';
|
||||
import { readFileToText } from '../../utils';
|
||||
import { Operations, OperationsCore, Organization } from '..';
|
||||
import { Operations, OperationsCore, Organization } from '../../business';
|
||||
import { CreateError } from '../../transitional';
|
||||
|
||||
export interface IGitHubRateLimit {
|
||||
export type GitHubRateLimit = {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
used: number;
|
||||
}
|
||||
};
|
||||
|
||||
// Installation redirect format:
|
||||
// /setup/app/APP_ID?installation_id=INSTALLATION_ID&setup_action=install
|
||||
|
@ -53,7 +53,7 @@ export class GitHubTokenManager {
|
|||
private _forceInstanceTokensToPurpose: AppPurposeTypes;
|
||||
private _allowReadOnlyFallbackToOtherInstallations: boolean;
|
||||
|
||||
static RegisterManagerForOperations(operations: OperationsCore, manager: GitHubTokenManager) {
|
||||
private static RegisterManagerForOperations(operations: OperationsCore, manager: GitHubTokenManager) {
|
||||
GitHubTokenManager._managersForOperations.set(operations, manager);
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,11 @@ export class GitHubTokenManager {
|
|||
this.#options = options;
|
||||
GitHubTokenManager._forceBackgroundTokens =
|
||||
executionEnvironment.isJob && !executionEnvironment.enableAllGitHubApps;
|
||||
GitHubTokenManager.RegisterManagerForOperations(options.operations, this);
|
||||
}
|
||||
|
||||
private operations() {
|
||||
return this.#options.operations as Operations;
|
||||
}
|
||||
|
||||
private getFallbackList(input: AppPurposeTypes[]) {
|
||||
|
@ -147,6 +152,8 @@ export class GitHubTokenManager {
|
|||
}
|
||||
|
||||
private getPurposeDisplayId(purpose: AppPurposeTypes) {
|
||||
// is this identical to the method getAppPurposeId?
|
||||
|
||||
const asCustom = purpose as ICustomAppPurpose;
|
||||
if (asCustom?.isCustomAppPurpose === true) {
|
||||
return asCustom.id;
|
||||
|
@ -189,9 +196,11 @@ export class GitHubTokenManager {
|
|||
preferredPurpose: AppPurposeTypes,
|
||||
organizationSettings: OrganizationSetting,
|
||||
appAuthenticationType: GitHubAppAuthenticationType
|
||||
): Promise<IAuthorizationHeaderValue> {
|
||||
): Promise<AuthorizationHeaderValue> {
|
||||
debug(
|
||||
`getOrganizationAuthorizationHeader(${organizationName}, ${preferredPurpose}, ${appAuthenticationType})`
|
||||
`getOrganizationAuthorizationHeader(${organizationName}, ${this.getPurposeDisplayId(
|
||||
preferredPurpose
|
||||
)}, ${appAuthenticationType})`
|
||||
);
|
||||
const installationIdPair = this.getPrioritizedOrganizationInstallationId(
|
||||
preferredPurpose,
|
||||
|
@ -200,8 +209,10 @@ export class GitHubTokenManager {
|
|||
appAuthenticationType
|
||||
);
|
||||
if (!installationIdPair) {
|
||||
throw new Error(
|
||||
`GitHubTokenManager: organization ${organizationName} does not have a configured GitHub App installation, or, the installation information is not in this environment. The API preferred purpose was ${preferredPurpose} with the selection type ${appAuthenticationType}.`
|
||||
throw CreateError.InvalidParameters(
|
||||
`GitHubTokenManager: organization ${organizationName} does not have a configured GitHub App installation, or, the installation information is not in this environment. The API preferred purpose was ${getAppPurposeId(
|
||||
preferredPurpose
|
||||
)} with the selection type ${appAuthenticationType}.`
|
||||
);
|
||||
}
|
||||
if (
|
||||
|
@ -255,7 +266,9 @@ export class GitHubTokenManager {
|
|||
);
|
||||
const value = await app.getInstallationToken(installationIdPair.installationId, organizationName);
|
||||
debug(
|
||||
`returned installation ID pair: installationId=${value?.installationId}, source=${value?.source}, purpose=${value?.purpose}`
|
||||
`returned installation ID pair: installationId=${value?.installationId}, source=${value?.source}, purpose=${this.getPurposeDisplayId(
|
||||
value?.purpose
|
||||
)}`
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
@ -264,7 +277,7 @@ export class GitHubTokenManager {
|
|||
appId: number,
|
||||
installationId: number,
|
||||
organizationName: string
|
||||
): Promise<IAuthorizationHeaderValue> {
|
||||
): Promise<AuthorizationHeaderValue> {
|
||||
const app = this._appsById.get(appId);
|
||||
if (!app) {
|
||||
throw new Error(`App ID=${appId} is not configured in this application instance`);
|
||||
|
@ -272,10 +285,50 @@ export class GitHubTokenManager {
|
|||
return app.getInstallationToken(installationId, organizationName);
|
||||
}
|
||||
|
||||
getAppForPurpose(purpose: AppPurposeTypes) {
|
||||
getAppForPurpose(purpose: AppPurposeTypes, organizationName?: string) {
|
||||
const asCustomPurpose = this.getCustomPurpose(purpose);
|
||||
if (asCustomPurpose?.getForOrganizationName) {
|
||||
const configForOrganization = asCustomPurpose.getForOrganizationName(organizationName);
|
||||
const appId = configForOrganization.appId;
|
||||
if (appId) {
|
||||
return this._appsById.get(appId);
|
||||
}
|
||||
} else if (asCustomPurpose?.getApplicationConfigurationForInitialization) {
|
||||
const config = asCustomPurpose.getApplicationConfigurationForInitialization();
|
||||
const appId = config.appId;
|
||||
if (appId) {
|
||||
return this._appsById.get(appId);
|
||||
}
|
||||
}
|
||||
return this._apps.get(purpose);
|
||||
}
|
||||
|
||||
getAnyConfiguredInstallationIdForAppId(operations: Operations, appId: number) {
|
||||
const orgs = operations.getOrganizations();
|
||||
for (const org of orgs) {
|
||||
const settings = org.getDynamicSettings();
|
||||
if (settings?.installations) {
|
||||
for (const { appId: appConfiguredId, installationId } of settings.installations) {
|
||||
if (appConfiguredId === appId) {
|
||||
return { installationId, organizationName: org.name };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAnyConfiguredInstallationIdForAnyApp(operations: Operations) {
|
||||
const orgs = operations.getOrganizations();
|
||||
for (const org of orgs) {
|
||||
const settings = org.getDynamicSettings();
|
||||
if (settings?.installations) {
|
||||
for (const { installationId, appId } of settings.installations) {
|
||||
return { installationId, organizationName: org.name, appId };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getInstallationIdForOrganization(purpose: AppPurposeTypes, organization: Organization) {
|
||||
const settings = organization.getDynamicSettings();
|
||||
if (settings?.installations) {
|
||||
|
@ -286,7 +339,18 @@ export class GitHubTokenManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
// CONSIDER: custom purposes could expose the installation ID here
|
||||
const asCustomPurpose = this.getCustomPurpose(purpose);
|
||||
if (asCustomPurpose?.getForOrganizationName) {
|
||||
const configForOrganization = asCustomPurpose.getForOrganizationName(organization.name);
|
||||
if (configForOrganization.slug) {
|
||||
throw CreateError.NotImplemented(
|
||||
`While a custom purpose is configured for the "${organization.name}" with the app ${configForOrganization.slug}, the installation ID is not yet available with this call. This is a known limitation.`
|
||||
);
|
||||
}
|
||||
throw CreateError.NotImplemented(
|
||||
`This custom purpose is not configured for the "${organization.name}" org with the app ${configForOrganization.slug}, the installation ID is not yet available with this call. This is a known limitation.`
|
||||
);
|
||||
}
|
||||
if (!organization.hasDynamicSettings) {
|
||||
throw CreateError.InvalidParameters(
|
||||
`Organization ${organization.name} does not have dynamic settings or purpose-directed configuration`
|
||||
|
@ -294,6 +358,74 @@ export class GitHubTokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
getAuthorizationHeaderForAnyApp(): AuthorizationHeaderValue {
|
||||
const anyConfigured = this.getAnyConfiguredInstallationIdForAnyApp(this.operations());
|
||||
if (anyConfigured) {
|
||||
return this.getInstallationAuthorizationHeader.bind(
|
||||
this,
|
||||
anyConfigured.appId,
|
||||
anyConfigured.installationId,
|
||||
anyConfigured.organizationName
|
||||
);
|
||||
}
|
||||
throw CreateError.InvalidParameters('No configured applications available.');
|
||||
}
|
||||
|
||||
async getAppInformation(purpose: AppPurposeTypes, organizationName?: string) {
|
||||
const appTokens = this.getAppForPurpose(purpose, organizationName);
|
||||
if (!appTokens) {
|
||||
throw CreateError.InvalidParameters(`No app configured yet for purpose ${purpose}`);
|
||||
}
|
||||
const slug = appTokens.slug;
|
||||
if (!slug) {
|
||||
throw CreateError.InvalidParameters(`No slug configured for purpose ${purpose}`);
|
||||
}
|
||||
return this.getAppInformationBySlug(this.operations(), slug);
|
||||
}
|
||||
|
||||
async getAppInformationBySlug(operations: Operations, slug: string) {
|
||||
let appId: number = null;
|
||||
for (const entry of this._appSlugs.entries()) {
|
||||
if (entry[1] === slug) {
|
||||
appId = entry[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
let authorizationHeader: GetAuthorizationHeader = null;
|
||||
// Have the app call itself via the slug-based API (works if it's a private single-org app)
|
||||
if (appId) {
|
||||
const anyConfiguredForApp = this.getAnyConfiguredInstallationIdForAppId(operations, appId);
|
||||
if (anyConfiguredForApp) {
|
||||
authorizationHeader = this.getInstallationAuthorizationHeader.bind(
|
||||
this,
|
||||
appId,
|
||||
anyConfiguredForApp.installationId,
|
||||
anyConfiguredForApp.organizationName
|
||||
);
|
||||
}
|
||||
}
|
||||
// Call using any configured app
|
||||
if (!authorizationHeader) {
|
||||
const anyConfigured = this.getAnyConfiguredInstallationIdForAnyApp(operations);
|
||||
if (anyConfigured) {
|
||||
authorizationHeader = this.getInstallationAuthorizationHeader.bind(
|
||||
this,
|
||||
anyConfigured.appId,
|
||||
anyConfigured.installationId,
|
||||
anyConfigured.organizationName
|
||||
);
|
||||
}
|
||||
}
|
||||
// Fallback to a static token
|
||||
if (!authorizationHeader) {
|
||||
authorizationHeader = operations.getPublicReadOnlyStaticToken.bind(operations);
|
||||
}
|
||||
const value = await operations.github.post(authorizationHeader, 'apps.getBySlug', {
|
||||
app_slug: slug,
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
async getRateLimitInformation(purpose: AppPurposeTypes, organization: Organization) {
|
||||
const settings = organization.getDynamicSettings();
|
||||
if (settings?.installations) {
|
||||
|
@ -312,7 +444,7 @@ export class GitHubTokenManager {
|
|||
NoCacheNoBackground
|
||||
);
|
||||
if (value?.rate) {
|
||||
return value.rate as IGitHubRateLimit;
|
||||
return value.rate as GitHubRateLimit;
|
||||
}
|
||||
console.warn(value);
|
||||
throw CreateError.InvalidParameters('No rate limit information returned');
|
||||
|
@ -348,12 +480,18 @@ export class GitHubTokenManager {
|
|||
const allPurposes = GitHubAppPurposes.AllAvailableAppPurposes;
|
||||
const fallbackPurposePriorities = this.getFallbackList(allPurposes);
|
||||
const customPurposes = allPurposes.filter((p) => (p as ICustomAppPurpose).isCustomAppPurpose === true);
|
||||
const matchingCustomPurposes =
|
||||
(preferredPurpose as ICustomAppPurpose)?.isCustomAppPurpose === true
|
||||
? customPurposes.filter(
|
||||
(cp) => (cp as ICustomAppPurpose).id === (preferredPurpose as ICustomAppPurpose).id
|
||||
)
|
||||
: customPurposes;
|
||||
let order =
|
||||
this.isForcingBackgroundJobType() === true
|
||||
? fallbackPurposePriorities
|
||||
: [preferredPurpose, ...fallbackPurposePriorities];
|
||||
if (appAuthenticationType === GitHubAppAuthenticationType.ForceSpecificInstallation) {
|
||||
order = [preferredPurpose, ...customPurposes];
|
||||
order = [preferredPurpose, ...matchingCustomPurposes];
|
||||
}
|
||||
for (const purpose of order) {
|
||||
let customAppPurpose = purpose as ICustomAppPurpose;
|
||||
|
@ -406,11 +544,12 @@ export class GitHubTokenManager {
|
|||
// Not base64-encoded, use the CreateFromString method.
|
||||
skipDecodingBase64 = true;
|
||||
}
|
||||
const slug = appConfig.slug;
|
||||
const friendlyName = customPurpose?.name || appConfig.description || 'Unknown';
|
||||
const baseUrl = appConfig.baseUrl;
|
||||
const app = skipDecodingBase64
|
||||
? GitHubAppTokens.CreateFromString(purpose, friendlyName, appId, key, baseUrl)
|
||||
: GitHubAppTokens.CreateFromBase64EncodedFileString(purpose, friendlyName, appId, key, baseUrl);
|
||||
? GitHubAppTokens.CreateFromString(purpose, slug, friendlyName, appId, key, baseUrl)
|
||||
: GitHubAppTokens.CreateFromBase64EncodedFileString(purpose, slug, friendlyName, appId, key, baseUrl);
|
||||
const hasCustomConfigurationByOrganization = customPurpose?.getForOrganizationName;
|
||||
const standardPurpose = purpose as AppPurpose;
|
||||
if (!hasCustomConfigurationByOrganization) {
|
|
@ -83,7 +83,7 @@ import type {
|
|||
} from '../interfaces';
|
||||
import initializeRepositoryProvider from '../entities/repository';
|
||||
import { tryGetImmutableStorageProvider } from '../lib/immutable';
|
||||
import { GitHubAppPurposes } from '../business/githubApps';
|
||||
import { GitHubAppPurposes } from '../lib/github/appPurposes';
|
||||
|
||||
const DefaultApplicationProfile: IApplicationProfile = {
|
||||
applicationName: 'Open Source Management Portal',
|
||||
|
@ -319,16 +319,6 @@ async function initializeAsync(
|
|||
}
|
||||
|
||||
function configureGitHubLibrary(cacheProvider: ICacheHelper, config): RestLibrary {
|
||||
if (
|
||||
config &&
|
||||
config.github &&
|
||||
config.github.operations &&
|
||||
!config.github.operations.centralOperationsToken
|
||||
) {
|
||||
debug(
|
||||
'WARNING: no central GitHub operations token is defined, some library operations may not succeed. ref: config.github.operations.centralOperationsToken var: GITHUB_CENTRAL_OPERATIONS_TOKEN'
|
||||
);
|
||||
}
|
||||
const libraryContext = new RestLibrary({
|
||||
config,
|
||||
cacheProvider,
|
||||
|
|
|
@ -14,15 +14,15 @@
|
|||
"@azure/identity": "4.0.0",
|
||||
"@azure/keyvault-secrets": "4.7.0",
|
||||
"@azure/service-bus": "7.9.3",
|
||||
"@azure/storage-blob": "12.16.0",
|
||||
"@azure/storage-queue": "12.15.0",
|
||||
"@azure/storage-blob": "12.17.0",
|
||||
"@azure/storage-queue": "12.16.0",
|
||||
"@octokit/auth-app": "6.0.1",
|
||||
"@octokit/plugin-paginate-graphql": "4.0.0",
|
||||
"@octokit/request": "8.1.4",
|
||||
"@octokit/request": "8.1.5",
|
||||
"@octokit/rest": "20.0.2",
|
||||
"@primer/octicons": "19.8.0",
|
||||
"app-root-path": "3.1.0",
|
||||
"applicationinsights": "2.9.0",
|
||||
"applicationinsights": "2.9.1",
|
||||
"async-prompt": "1.0.1",
|
||||
"axios": "1.6.1",
|
||||
"basic-auth": "2.0.1",
|
||||
|
@ -73,7 +73,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "2.8.16",
|
||||
"@types/debug": "4.1.11",
|
||||
"@types/debug": "4.1.12",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/express-session": "1.17.10",
|
||||
"@types/jest": "29.5.8",
|
||||
|
@ -98,7 +98,7 @@
|
|||
"cspell": "8.0.0",
|
||||
"eslint": "8.53.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-n": "16.3.0",
|
||||
"eslint-plugin-n": "16.3.1",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"husky": "8.0.3",
|
||||
"jest": "29.7.0",
|
||||
|
@ -501,9 +501,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@azure/storage-blob": {
|
||||
"version": "12.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.16.0.tgz",
|
||||
"integrity": "sha512-jz33rUSUGUB65FgYrTRgRDjG6hdPHwfvHe+g/UrwVG8MsyLqSxg9TaW7Yuhjxu1v1OZ5xam2NU6+IpCN0xJO8Q==",
|
||||
"version": "12.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.17.0.tgz",
|
||||
"integrity": "sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ==",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-http": "^3.0.0",
|
||||
|
@ -531,9 +531,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@azure/storage-queue": {
|
||||
"version": "12.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-queue/-/storage-queue-12.15.0.tgz",
|
||||
"integrity": "sha512-qotyfMcmocaT1r9bfX7RyQQnkRJcTXGSG/m6LIchtEsNmqLu3ulVqkOlNM+O0XP9wwJ6Da1XQbWz5jQtrDe3DQ==",
|
||||
"version": "12.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-queue/-/storage-queue-12.16.0.tgz",
|
||||
"integrity": "sha512-HzwzMsNAh2PwLtx9WfXndj9elUr6duDFak5CjM6BxdWhLqf37VywPCWmEzjxuFfrq30c1T34+MjMXnN6YgqRUw==",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-http": "^3.0.0",
|
||||
|
@ -2528,9 +2528,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.4.tgz",
|
||||
"integrity": "sha512-M0aaFfpGPEKrg7XoA/gwgRvc9MSXHRO2Ioki1qrPDbl1e9YhjIwVoHE7HIKmv/m3idzldj//xBujcFNqGX6ENA==",
|
||||
"version": "8.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.5.tgz",
|
||||
"integrity": "sha512-zVKbNbX1xUluD9ZR4/tPs1yuYrK9xeh5fGZUXA6u04XGsTvomg0YO8/ZUC0FqAd49hAOEMFPAVUTh+2lBhOhLA==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^9.0.0",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
|
@ -2944,9 +2944,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.11.tgz",
|
||||
"integrity": "sha512-R2qflTjHDs4CL6D/6TkqBeIHr54WzZfIxN729xvCNlYIVp2LknlnCro5Yo3frNaX2E5gO9pZ3/QAPVdGmu+q9w==",
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/ms": "*"
|
||||
|
@ -3684,9 +3684,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/applicationinsights": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.9.0.tgz",
|
||||
"integrity": "sha512-W90WNjtvZ10GUInpkyNM0xBGe2qRYChHhdb44SE5KU7hXtCZLxs3IZjWw1gJINQem0qGAgtZlxrVvKPj5SlTbQ==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.9.1.tgz",
|
||||
"integrity": "sha512-hrpe/OvHFZlq+SQERD1fxaYICyunxzEBh9SolJebzYnIXkyA9zxIR87dZAh+F3+weltbqdIP8W038cvtpMNhQg==",
|
||||
"dependencies": {
|
||||
"@azure/core-auth": "^1.5.0",
|
||||
"@azure/core-rest-pipeline": "1.10.1",
|
||||
|
@ -4180,6 +4180,18 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/builtins": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
|
||||
|
@ -5701,9 +5713,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-n": {
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.3.0.tgz",
|
||||
"integrity": "sha512-/XZLH5CUXGK3laz3xYFNza8ZxLCq8ZNW6MsVw5z3d5hc2AwZzi0fPiySFZHQTdVDOHGs2cGv91aqzWmgBdq2gQ==",
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.3.1.tgz",
|
||||
"integrity": "sha512-w46eDIkxQ2FaTHcey7G40eD+FhTXOdKudDXPUO2n9WNcslze/i/HT2qJ3GXjHngYSGDISIgPNhwGtgoix4zeOw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
|
@ -5711,6 +5723,7 @@
|
|||
"eslint-plugin-es-x": "^7.1.0",
|
||||
"get-tsconfig": "^4.7.0",
|
||||
"ignore": "^5.2.4",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-core-module": "^2.12.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"resolve": "^1.22.2",
|
||||
|
@ -6942,6 +6955,21 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"builtin-modules": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
|
@ -11818,9 +11846,9 @@
|
|||
}
|
||||
},
|
||||
"@azure/storage-blob": {
|
||||
"version": "12.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.16.0.tgz",
|
||||
"integrity": "sha512-jz33rUSUGUB65FgYrTRgRDjG6hdPHwfvHe+g/UrwVG8MsyLqSxg9TaW7Yuhjxu1v1OZ5xam2NU6+IpCN0xJO8Q==",
|
||||
"version": "12.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.17.0.tgz",
|
||||
"integrity": "sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ==",
|
||||
"requires": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-http": "^3.0.0",
|
||||
|
@ -11844,9 +11872,9 @@
|
|||
}
|
||||
},
|
||||
"@azure/storage-queue": {
|
||||
"version": "12.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-queue/-/storage-queue-12.15.0.tgz",
|
||||
"integrity": "sha512-qotyfMcmocaT1r9bfX7RyQQnkRJcTXGSG/m6LIchtEsNmqLu3ulVqkOlNM+O0XP9wwJ6Da1XQbWz5jQtrDe3DQ==",
|
||||
"version": "12.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/storage-queue/-/storage-queue-12.16.0.tgz",
|
||||
"integrity": "sha512-HzwzMsNAh2PwLtx9WfXndj9elUr6duDFak5CjM6BxdWhLqf37VywPCWmEzjxuFfrq30c1T34+MjMXnN6YgqRUw==",
|
||||
"requires": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-http": "^3.0.0",
|
||||
|
@ -13483,9 +13511,9 @@
|
|||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.4.tgz",
|
||||
"integrity": "sha512-M0aaFfpGPEKrg7XoA/gwgRvc9MSXHRO2Ioki1qrPDbl1e9YhjIwVoHE7HIKmv/m3idzldj//xBujcFNqGX6ENA==",
|
||||
"version": "8.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.5.tgz",
|
||||
"integrity": "sha512-zVKbNbX1xUluD9ZR4/tPs1yuYrK9xeh5fGZUXA6u04XGsTvomg0YO8/ZUC0FqAd49hAOEMFPAVUTh+2lBhOhLA==",
|
||||
"requires": {
|
||||
"@octokit/endpoint": "^9.0.0",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
|
@ -13832,9 +13860,9 @@
|
|||
}
|
||||
},
|
||||
"@types/debug": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.11.tgz",
|
||||
"integrity": "sha512-R2qflTjHDs4CL6D/6TkqBeIHr54WzZfIxN729xvCNlYIVp2LknlnCro5Yo3frNaX2E5gO9pZ3/QAPVdGmu+q9w==",
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/ms": "*"
|
||||
|
@ -14421,9 +14449,9 @@
|
|||
"integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA=="
|
||||
},
|
||||
"applicationinsights": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.9.0.tgz",
|
||||
"integrity": "sha512-W90WNjtvZ10GUInpkyNM0xBGe2qRYChHhdb44SE5KU7hXtCZLxs3IZjWw1gJINQem0qGAgtZlxrVvKPj5SlTbQ==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.9.1.tgz",
|
||||
"integrity": "sha512-hrpe/OvHFZlq+SQERD1fxaYICyunxzEBh9SolJebzYnIXkyA9zxIR87dZAh+F3+weltbqdIP8W038cvtpMNhQg==",
|
||||
"requires": {
|
||||
"@azure/core-auth": "^1.5.0",
|
||||
"@azure/core-rest-pipeline": "1.10.1",
|
||||
|
@ -14792,6 +14820,12 @@
|
|||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||
"dev": true
|
||||
},
|
||||
"builtins": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
|
||||
|
@ -15892,9 +15926,9 @@
|
|||
}
|
||||
},
|
||||
"eslint-plugin-n": {
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.3.0.tgz",
|
||||
"integrity": "sha512-/XZLH5CUXGK3laz3xYFNza8ZxLCq8ZNW6MsVw5z3d5hc2AwZzi0fPiySFZHQTdVDOHGs2cGv91aqzWmgBdq2gQ==",
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.3.1.tgz",
|
||||
"integrity": "sha512-w46eDIkxQ2FaTHcey7G40eD+FhTXOdKudDXPUO2n9WNcslze/i/HT2qJ3GXjHngYSGDISIgPNhwGtgoix4zeOw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
|
@ -15902,6 +15936,7 @@
|
|||
"eslint-plugin-es-x": "^7.1.0",
|
||||
"get-tsconfig": "^4.7.0",
|
||||
"ignore": "^5.2.4",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-core-module": "^2.12.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"resolve": "^1.22.2",
|
||||
|
@ -16749,6 +16784,15 @@
|
|||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
|
||||
},
|
||||
"is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"builtin-modules": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
|
|
|
@ -11,7 +11,7 @@ import { getProviders } from '../../transitional';
|
|||
import {
|
||||
OrganizationSetting,
|
||||
IBasicGitHubAppInstallation,
|
||||
SpecialTeam,
|
||||
SystemTeam,
|
||||
} from '../../entities/organizationSettings/organizationSetting';
|
||||
import { IndividualContext } from '../../business/user';
|
||||
import { Operations, Organization } from '../../business';
|
||||
|
@ -296,22 +296,25 @@ router.post(
|
|||
// explicitly now allowing globalSudo to be set here
|
||||
throw new Error(`Unsupported team type: ${teamType}`);
|
||||
}
|
||||
let specialTeamType: SpecialTeam = null;
|
||||
let specialTeamType: SystemTeam = null;
|
||||
switch (teamType) {
|
||||
case 'everyone':
|
||||
specialTeamType = SpecialTeam.Everyone;
|
||||
specialTeamType = SystemTeam.Everyone;
|
||||
break;
|
||||
case 'openAccess':
|
||||
specialTeamType = SystemTeam.OpenAccess;
|
||||
break;
|
||||
case 'systemAdmin':
|
||||
specialTeamType = SpecialTeam.SystemAdmin;
|
||||
specialTeamType = SystemTeam.SystemAdmin;
|
||||
break;
|
||||
case 'systemWrite':
|
||||
specialTeamType = SpecialTeam.SystemWrite;
|
||||
specialTeamType = SystemTeam.SystemWrite;
|
||||
break;
|
||||
case 'systemRead':
|
||||
specialTeamType = SpecialTeam.SystemRead;
|
||||
specialTeamType = SystemTeam.SystemRead;
|
||||
break;
|
||||
case 'sudo':
|
||||
specialTeamType = SpecialTeam.Sudo;
|
||||
specialTeamType = SystemTeam.Sudo;
|
||||
break;
|
||||
// case 'globalSudo':
|
||||
// specialTeamType = SpecialTeam.GlobalSudo;
|
||||
|
|
|
@ -386,7 +386,7 @@ export async function calculateGroupedPermissionsViewForRepository(repository: R
|
|||
collaborators, // Collaborator[]
|
||||
outsideCollaborators, // Collaborator[]
|
||||
} = await calculateRepoPermissions(organization, repository);
|
||||
const systemTeams = combineAllTeams(organization.specialRepositoryPermissionTeams); // number[]
|
||||
const systemTeams = combineAllTeams(organization.specialSystemTeams); // number[]
|
||||
const teamBasedPermissions = consolidateTeamPermissions(permissions, systemTeams); // busted?
|
||||
const groupedPermissions = slicePermissionsForView(
|
||||
filterSystemTeams(teamsFilterType.systemTeamsExcluded, systemTeams, permissions)
|
||||
|
@ -443,7 +443,7 @@ router.get(
|
|||
organization,
|
||||
repository
|
||||
);
|
||||
const systemTeams = combineAllTeams(organization.specialRepositoryPermissionTeams);
|
||||
const systemTeams = combineAllTeams(organization.specialSystemTeams);
|
||||
const teamBasedPermissions = consolidateTeamPermissions(permissions, systemTeams);
|
||||
const title = `${repository.name} - Repository`;
|
||||
const details = await repository.organization.getDetails();
|
||||
|
|
|
@ -155,10 +155,11 @@ router.get(
|
|||
asyncHandler(async function (req: ILocalRequest, res: Response, next: NextFunction) {
|
||||
const team2 = req.team2 as Team;
|
||||
const organization = req.organization as Organization;
|
||||
// The broad access "all members" team is always open for automatic joining without
|
||||
// approval. This short circuit is to show that option.
|
||||
const broadAccessTeams = new Set(organization.broadAccessTeams);
|
||||
if (broadAccessTeams.has(team2.id)) {
|
||||
// The broad access "all members" team and any "easy access" teams are
|
||||
// always open for automatic joining without approval. This short circuit
|
||||
// is to show that option.
|
||||
const allowSelfJoinTeams = new Set([...organization.broadAccessTeams, ...organization.openAccessTeams]);
|
||||
if (allowSelfJoinTeams.has(team2.id)) {
|
||||
req.individualContext.webContext.render({
|
||||
view: 'org/team/join',
|
||||
title: `Join ${team2.name}`,
|
||||
|
@ -255,7 +256,8 @@ export async function submitTeamJoinRequest(
|
|||
): Promise<ITeamJoinRequestSubmitOutcome> {
|
||||
const { approvalProvider, config, graphProvider, mailProvider, insights, operations } = providers;
|
||||
const organization = team.organization;
|
||||
const broadAccessTeams = new Set(organization.broadAccessTeams);
|
||||
const { broadAccessTeams, openAccessTeams } = organization;
|
||||
const allowSelfJoinTeams = new Set([...broadAccessTeams, ...openAccessTeams]);
|
||||
if (!approvalProvider) {
|
||||
return { error: new Error('No approval provider available') };
|
||||
}
|
||||
|
@ -263,8 +265,17 @@ export async function submitTeamJoinRequest(
|
|||
if (!username) {
|
||||
return { error: new Error('Active context required') };
|
||||
}
|
||||
if (broadAccessTeams.has(team.id)) {
|
||||
if (allowSelfJoinTeams.has(team.id)) {
|
||||
try {
|
||||
const eventName = broadAccessTeams.includes(team.id) ? 'JoinBroadAccessTeam' : 'JoinOpenAccessTeam';
|
||||
insights?.trackEvent({
|
||||
name: eventName,
|
||||
properties: {
|
||||
organization: organization.name,
|
||||
id: team.id,
|
||||
slug: team.slug,
|
||||
},
|
||||
});
|
||||
await team.addMembership(username);
|
||||
} catch (error) {
|
||||
insights?.trackEvent({
|
||||
|
|
|
@ -31,7 +31,7 @@ interface ICustomDataEventName {
|
|||
|
||||
export default class AutomaticTeamsWebhookProcessor implements WebhookProcessor {
|
||||
processOrgSpecialTeams(organization: Organization) {
|
||||
const specialTeams = organization.specialRepositoryPermissionTeams;
|
||||
const specialTeams = organization.specialSystemTeams;
|
||||
const specials = [];
|
||||
const specialTeamIds = new Set<number>();
|
||||
const specialTeamLevels = new Map<number, string>();
|
||||
|
|
Загрузка…
Ссылка в новой задаче