Skip to content

Commit

Permalink
Uncontrolled organization fix
Browse files Browse the repository at this point in the history
For modern apps using GitHub Apps instead of PATs or OAuth tokens,
this makes sure that the Uncontrolled Organization method can still
be used to retrieve other GitHub organizations that are not managed
by the apps for purposes such as pulling org details and basics.
  • Loading branch information
jeffwilcox committed Dec 5, 2023
1 parent d632013 commit c63dc75
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 40 deletions.
1 change: 1 addition & 0 deletions api/client/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ router.use(
return next();
} catch (noOrgError) {
if (ErrorHelper.IsNotFound(noOrgError)) {
// Could be either the org truly does not exist, OR, it's uncontrolled.
if (await isUnmanagedOrganization(providers, orgName)) {
res.status(204);
res.end();
Expand Down
99 changes: 59 additions & 40 deletions business/operations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ export type GetInvisibleOrganizationOptions = {
storeInstanceByName?: boolean;
};

type CreateOrganizationOptions = {
settings: OrganizationSetting;
appAuthenticationType: GitHubAppAuthenticationType;
asUncontrolledPublicOnly?: boolean;
};

export class Operations
extends OperationsCore
implements
Expand Down Expand Up @@ -338,32 +344,46 @@ export class Operations
return Array.from(this._organizationIds.keys());
}

private createOrganization(
name: string,
settings: OrganizationSetting,
appAuthenticationType: GitHubAppAuthenticationType
): Organization {
private createOrganization(name: string, options: CreateOrganizationOptions): Organization {
name = name.toLowerCase();
if (!options) {
throw CreateError.ParameterRequired('options');
}
const { settings, appAuthenticationType, asUncontrolledPublicOnly } = options;
if (!settings) {
throw new Error(`This application is not configured for the ${name} organization`);
throw CreateError.InvalidParameters(
`This application does not have configuration information for the ${name} organization`
);
}
const ownerToken = settings.getOwnerToken();
const hasDynamicSettings =
this._dynamicOrganizationIds &&
settings.organizationId &&
this._dynamicOrganizationIds.has(Number(settings.organizationId));
let configuredGetAuthorizationHeader: GetAuthorizationHeader = this.getAuthorizationHeader.bind(
this,
name,
settings,
ownerToken,
appAuthenticationType
);
let forcedGetAuthorizationHeader: GetAuthorizationHeader = this.getAuthorizationHeader.bind(
this,
name,
settings,
ownerToken,
GitHubAppAuthenticationType.ForceSpecificInstallation
);
if (!ownerToken && asUncontrolledPublicOnly) {
configuredGetAuthorizationHeader = this.getPublicAuthorizationToken();
forcedGetAuthorizationHeader = configuredGetAuthorizationHeader;
}
return new Organization(
this,
name,
settings,
this.getAuthorizationHeader.bind(this, name, settings, ownerToken, appAuthenticationType),
this.getAuthorizationHeader.bind(
this,
name,
settings,
ownerToken,
GitHubAppAuthenticationType.ForceSpecificInstallation
),
configuredGetAuthorizationHeader,
forcedGetAuthorizationHeader,
hasDynamicSettings
);
}
Expand All @@ -384,11 +404,10 @@ export class Operations
settings = dos;
}
}
const organization = this.createOrganization(
name,
const organization = this.createOrganization(name, {
settings,
GitHubAppAuthenticationType.BestAvailable
);
appAuthenticationType: GitHubAppAuthenticationType.BestAvailable,
});
organizations.set(name, organization);
}
this._organizations = organizations;
Expand All @@ -407,11 +426,10 @@ export class Operations
for (let i = 0; i < list.length; i++) {
const settings = list[i];
if (settings && settings.name && settings.name.toLowerCase() === lowercase) {
return this.createOrganization(
lowercase,
OrganizationSetting.CreateFromStaticSettings(settings),
GitHubAppAuthenticationType.BestAvailable
);
return this.createOrganization(lowercase, {
settings: OrganizationSetting.CreateFromStaticSettings(settings),
appAuthenticationType: GitHubAppAuthenticationType.BestAvailable,
});
}
}
}
Expand All @@ -427,11 +445,10 @@ export class Operations
}

getUnconfiguredOrganization(settings: OrganizationSetting): Organization {
return this.createOrganization(
settings.organizationName.toLowerCase(),
return this.createOrganization(settings.organizationName.toLowerCase(), {
settings,
GitHubAppAuthenticationType.BestAvailable
);
appAuthenticationType: GitHubAppAuthenticationType.BestAvailable,
});
}

// An invisible organization does not appear in the cross-organization
Expand Down Expand Up @@ -462,7 +479,10 @@ export class Operations
dynamicSettings = options.settings;
}
const authenticationType = options?.authenticationType || GitHubAppAuthenticationType.BestAvailable;
const organization = this.createOrganization(name, dynamicSettings, authenticationType);
const organization = this.createOrganization(name, {
settings: dynamicSettings,
appAuthenticationType: authenticationType,
});
if (!options || options?.storeInstanceByName) {
this._invisibleOrganizations.set(name, organization);
}
Expand All @@ -480,11 +500,12 @@ export class Operations
}
const emptySettings = new OrganizationSetting();
emptySettings.operationsNotes = `Uncontrolled Organization - ${organizationName}`;
const org = this.createOrganization(
organizationName,
emptySettings,
GitHubAppAuthenticationType.ForceSpecificInstallation
);
const asUncontrolledPublicOnly = true;
const org = this.createOrganization(organizationName, {
settings: emptySettings,
appAuthenticationType: GitHubAppAuthenticationType.ForceSpecificInstallation,
asUncontrolledPublicOnly,
});
this._uncontrolledOrganizations.set(organizationName, org);
org.uncontrolled = true;
return org;
Expand All @@ -501,11 +522,10 @@ export class Operations
`Uncontrolled public organization - ${organizationName}`,
organizationId
);
const org = this.createOrganization(
organizationName,
emptySettings,
GitHubAppAuthenticationType.ForceSpecificInstallation
);
const org = this.createOrganization(organizationName, {
settings: emptySettings,
appAuthenticationType: GitHubAppAuthenticationType.ForceSpecificInstallation,
});
this._uncontrolledOrganizations.set(organizationName, org);
org.uncontrolled = true;
return org;
Expand Down Expand Up @@ -813,8 +833,7 @@ export class Operations
const lc = name.toLowerCase();
const organization = this.organizations.get(lc);
if (!organization) {
// will this impact things?
throw CreateError.InvalidParameters(`Could not find configuration for the "${name}" organization.`);
throw CreateError.NotFound(`Could not find configuration for the "${name}" organization.`);
}
return organization;
}
Expand Down

0 comments on commit c63dc75

Please sign in to comment.