async function getOrganizationData()

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;
}