in jobs/reports/organizations.ts [121:393]
async function getOrganizationData(context: IReportsContext) {
const operations = context.operations as Operations;
const names = operations.getOrganizationOriginalNames().sort((a, b) => { return a.localeCompare(b, 'en', {'sensitivity': 'base'});});
for (const orgName of names) {
const organization = operations.organizations.get(orgName.toLowerCase()) as Organization;
if (!organization) {
console.warn(`Cannot locate ${orgName} at runtime`);
continue;
}
try {
if (!context.organizationData[orgName]) {
context.organizationData[orgName] = {};
}
console.log(`Organization: ${orgName}`);
// Organization context
const organizationContext: IReportOrganizationContext = {
organization: organization,
issues: {},
definitionsUsed: new Set()
};
const data = context.organizationData[orgName];
data.organizationContext = organizationContext;
function githubDirectLink(content, prefix?, suffix?, query?, alternateForOrgName?) {
const reposUrl = context.config.urls.repos;
const campaignSettings = context.settings.campaign;
const q = {
utm_source: campaignSettings.source,
utm_medium: campaignSettings.medium,
utm_campaign: campaignSettings.campaign,
utm_content: content,
go_github: null,
go_github_prefix: undefined,
go_github_query: undefined,
};
if (prefix) {
q.go_github_prefix = prefix;
}
if (suffix) {
q.go_github = suffix;
}
if (query) {
q.go_github_query = query;
}
return reposUrl + (alternateForOrgName || orgName) + '?' + querystring.stringify(q);
}
const organizationAdministrators = await getOrganizationAdministrators(organization);
const admins = await filterOrganizationAdministrators(context, organizationContext, organizationAdministrators);
data.administrators = admins;
await ensureGitHubFullNames(context, admins);
const unlinkedMembers = await getUnlinkedOrganizationMembers(context, organization);
data.unlinkedMembers = unlinkedMembers;
await ensureGitHubFullNames(context, unlinkedMembers as unknown as IAdministratorBasics[]);
// Configured private engineering org message
if (organization.privateEngineering) {
addOrganizationWarning(context, organizationContext, `Private engineering happens in the ${organization.name} GitHub organization. Consider an approved internal engineering system. This report is designed to help drive visibility for organizations involved in open source work on GitHub.com. As a result, some of the wording may not be appropriate for private engineering scenarios. Do share any feedback with the team. The repository-specific reports are only provided to org owners in this scenario.`);
}
// Configured "open source", external members org message
if (organization.externalMembersPermitted) {
addOrganizationWarning(context, organizationContext, `External members permitted: Your org, ${organization.name}, may permit members who are not linked. While most organizations require that all members have links, this org may be special. In the short term please identify employees and ask them to link their accounts. Longer term, this alert can be removed for this organization to reduce any noise. Please send feedback and your preferences in this space to opensource@microsoft.com.`);
}
// Org issue: unlinked owners or sudo users (administrators)
const adminsByType = organizationContext.administratorsByType;
if (adminsByType.unlinked.length) {
addOrganizationWarning(context, organizationContext, `This organization has ${adminsByType.unlinked.length} unlinked owners`);
}
const recipients = [];
if (adminsByType.linked.length) {
for (let i = 0; i < adminsByType.linked.length; i++) {
const adminEntry = adminsByType.linked[i];
const link = adminEntry.link as ICorporateLink;
if (link.serviceAccountMail) {
// Mails are only being sent to actual linked accounts at this time
/*
contactMethod = {
type: 'mail',
value: link.serviceAccountMail,
};
*/
} else if (link.corporateUsername) {
const contactMethod = {
type: 'upn',
value: link.corporateUsername,
reasons: [getReasonForRecipient(adminEntry, orgName)],
};
recipients.push(contactMethod);
} else {
console.warn(`Unable to identify the proper contact method for a linked administrator in the ${orgName} org`);
}
}
}
organizationContext.recipients = recipients;
// Org issue: too many owners
const owners = data.administrators.filter(member => { return member.owner; });
data.owners = owners;
// Review owners
const systemAccountOwnerUsernames = new Set(context.config && context.config.github && context.config.github.systemAccounts ? context.config.github.systemAccounts.logins : []);
const standardOwners = owners.filter(owner => { return !systemAccountOwnerUsernames.has(owner.login); });
//const systemAccountOwners = owners.filter(owner => { return systemAccountOwnerUsernames.has(owner.login); });
ownerBucket('reviewOwners', standardOwners);
// commenting out to reduce the size of reports...
// CONSIDER: enable configuration in this space
// ownerBucket('reviewSystemOwners', systemAccountOwners);
const tooMany = context.settings.tooManyOrgOwners || 5;
if (standardOwners.length > tooMany) {
addOrganizationWarning(context, organizationContext, `This organization has too many owners, increasing the chance of data loss, configuration problems and improper use of team permissions. Please limit the organization to under ${tooMany} direct owners.`);
}
// Review sudoers
const sudoers = data.administrators.filter(member => { return member.sudo && !member.owner && !systemAccountOwnerUsernames.has(member.login); });
data.sudoers = sudoers;
ownerBucket('reviewSudoers', sudoers);
function ownerBucket(definitionName, list) {
// Do not prepare this report type if it is empty
if (!list || !list.length) {
return;
}
const bucket = getOrganizationIssuesType(context, organizationContext, definitionName);
for (let x = 0; x < list.length; x++) {
const ownerEntry = Object.assign({
name: orgName,
}, list[x]);
// Role
let role = 'Unknown';
const roles = [];
if (ownerEntry.owner) {
roles.push('Owner');
}
if (ownerEntry.sudo) {
roles.push('Sudo owner');
}
if (ownerEntry.link && ownerEntry.link.serviceAccount) {
roles.push('Service account');
}
if (roles.length > 0) {
role = roles.join(', ');
}
ownerEntry.role = role;
// Actions
ownerEntry.actions = {
actions: [
{
text: 'Change role',
link: githubDirectLink('ownerChangeRole', 'orgs', 'people', 'query=' + ownerEntry.login),
},
],
};
// Link information
let fullName = null;
let corporateId = null;
if (ownerEntry.link) {
fullName = ownerEntry.link.aadname || ownerEntry.link.aadupn;
corporateId = ownerEntry.link.aadupn;
if (ownerEntry.link.serviceAccount && ownerEntry.link.serviceAccountMail) {
fullName = {
link: 'mailto:' + ownerEntry.link.serviceAccountMail,
text: fullName,
};
}
} else {
fullName = {
color: 'red',
text: 'Not linked',
};
corporateId = fullName;
ownerEntry.actions.actions.push({
link: githubDirectLink('ownerProfile', null, null, null, ownerEntry.login),
text: 'View profile',
});
ownerEntry.actions.actions.push({
text: 'Remove',
link: githubDirectLink('ownerRemove', 'orgs', 'people', `query=${ownerEntry.login}`),
});
ownerEntry.actions.actions.push(createAskToLinkAction(ownerEntry));
}
ownerEntry.fullName = fullName;
ownerEntry.corporateId = corporateId;
bucket.rows.push(ownerEntry);
}
}
// Unlinked members
if (data.unlinkedMembers.length) {
addOrganizationWarning(context, organizationContext, `This organization has ${data.unlinkedMembers.length} unlinked members`);
const bucket = getOrganizationIssuesType(context, organizationContext, 'unlinkedMembers');
for (let x = 0; x < data.unlinkedMembers.length; x++) {
const unlinked = Object.assign({}, data.unlinkedMembers[x]);
unlinked.actions = {
actions: [
{
link: githubDirectLink('unlinkedProfile', null, null, null, unlinked.login),
text: 'Review profile',
},
{
text: 'Remove',
link: githubDirectLink('unlinkedRemove', 'orgs', 'people', `query=${unlinked.login}`),
},
createAskToLinkAction(unlinked),
],
};
bucket.rows.push(unlinked);
}
}
const info = await getOrganizationDetails(organization);
data.info = info;
const fixMemberPrivilegesActions = [
{
link: githubDirectLink('reduceMemberPrivileges', 'organizations', 'settings/member_privileges'),
text: 'Reduce member privileges',
}
];
const fixOrganizationProfileActions = [
{
link: githubDirectLink('editOrganizationProfile', 'organizations', 'settings/profile'),
text: 'Edit organization profile',
}
];
const cleanupRepoActions = [
{
link: githubDirectLink('cleanupOldRepos'),
text: 'Cleanup old repositories',
}
];
// Org issue: members can create repositories
if (info.members_can_create_repositories) {
addOrganizationWarning(context, organizationContext, {
text: 'This organization allows members to directly create repositories on GitHub.com',
actions: fixMemberPrivilegesActions,
});
}
// Org issue: no org e-mail
if (!info.email) {
addOrganizationWarning(context, organizationContext, {
text: 'No e-mail address has been provided for any public questions about your organization',
actions: fixOrganizationProfileActions,
});
}
// Org issue: no description
if (!info.description) {
addOrganizationWarning(context, organizationContext, {
text: 'No organization description is provided',
actions: fixOrganizationProfileActions,
});
}
// Org issue: members all get admin or write access
if (info.default_repository_permission === 'write') {
addOrganizationWarning(context, organizationContext, {
text: 'All organization members receive permission to directly commit to all repos as well as accept pull requests.',
actions: fixMemberPrivilegesActions,
});
} else if (info.default_repository_permission === 'admin') {
addOrganizationWarning(context, organizationContext, {
text: 'All organization members receive administrative access to all repos. This practice is strongly discouraged.',
actions: fixMemberPrivilegesActions,
});
}
// Org issue: private repo utilization rate
const tooFewPrivateRepos = context.settings.orgPercentAvailablePrivateRepos || 0.25;
if (info.plan && info.plan.private_repos) {
const privateCap = info.plan.private_repos * (1 - tooFewPrivateRepos);
if (info.owned_private_repos > privateCap) {
const availablePrivateRepos = info.plan.private_repos - info.owned_private_repos;
addOrganizationWarning(context, organizationContext, {
text: `Private repos are running out: ${availablePrivateRepos} available out of plan limit of ${info.plan.private_repos}.`,
color: 'red',
actions: cleanupRepoActions,
});
}
}
} catch (error) {
console.log('Organizations error:');
console.warn(error);
}
}
return context;
}