async lockdownIfNecessary()

in features/newRepositoryLockdown.ts [294:539]


  async lockdownIfNecessary(action: 'created' | 'transferred', username: string, thirdPartyId: number, transferSourceRepositoryLogin: string): Promise<boolean> {
    const lockdownLog: string[] = [];
    // reconfirm that the new repository system is enabled for this organization
    if (!this.organization.isNewRepositoryLockdownSystemEnabled()) {
      return false;
    }
    const companySpecific = getCompanySpecificDeployment();
    const lockdownForks = this.organization.isForkLockdownSystemEnabled();
    const lockdownTransfers = this.organization.isTransferLockdownSystemEnabled();
    lockdownLog.push(`Confirmed that the ${this.organization.name} organization has opted in to the new repository lockdown system`);
    if (lockdownForks) {
      lockdownLog.push('Confirmed that the additional fork lockdown feature is enabled for this org');
    }
    if (lockdownTransfers) {
      lockdownLog.push('Confirmed that the additional transfer lockdown feature is enabled for this org');
    }
    const setupUrl = `${this.organization.absoluteBaseUrl}wizard?existingreponame=${this.repository.name}&existingrepoid=${this.repository.id}`;
    let isTransfer = action === 'transferred';
    if (isTransfer && !lockdownTransfers) {
      return false; // no need to do special transfer logic
    }
    if (isTransfer && transferSourceRepositoryLogin) {
      const isInternalTransfer = this.operations.isManagedOrganization(transferSourceRepositoryLogin);
      if (isInternalTransfer) {
        // BUSINESS RULE: If the organization is configured in the system, no need to lock it down...
        // CONSIDER: should there be a feature flag for this behavior, to allow managed-to-managed org transfers without lockdown?
        // CONSIDER: notify operations that a transfer happened
        return false;
      }
    }
    const lowercaseUsername = username.toLowerCase();
    // any repository created by a bot *is ok* and will not be locked down. If this is an issue, having an approved list of permitted bots to create repos would be one way to approach this loophole. Non-bot users cannot have brackets in their names.
    if (lowercaseUsername.includes(botBracket)) {
      // CONSIDER: send operations an e-mail FYI when a bot account is used?
      return false;
    }
    lockdownLog.push(`Confirmed that the repository was not ${action} by a bot`);
    // a repository created by one of the operations accounts in the allowed list is OK and will not be locked down
    const systemAccounts = new Set(this.operations.systemAccountsByUsername.map(username => username.toLowerCase()));
    if (systemAccounts.has(lowercaseUsername)) {
      return false;
    }
    lockdownLog.push(`Confirmed that the repository was not ${action} by any of the system accounts: ${Array.from(systemAccounts.values()).join(', ')}`);
    await this.lockdownRepository(lockdownLog, systemAccounts, username);
    let link: ICorporateLink = null;
    try {
      link = await this.operations.getLinkByThirdPartyId(thirdPartyId.toString());
    } catch (noLinkError) {
      lockdownLog.push(`No corporate link available for the GitHub username ${username} that created the repository`);
    }
    let isForkAdministratorLocked = false;
    let isForkParentManagedBySystem = false;
    let upstreamLogin = this.repository.parent?.owner?.login;
    if (!upstreamLogin && this.repository?.fork === true) {
      const moreEntity = await this.repository.getDetails();
      upstreamLogin = moreEntity?.parent?.owner?.login;
    }
    try {
      // Repository metadata is used to lock down the security of the repository system. Only
      // a complete system administrator or the initial creator of a repository is able to
      // complete the initial repository setup process.
      let repositoryMetadata: RepositoryMetadataEntity = null;
      try {
        repositoryMetadata = await this.repositoryMetadataProvider.getRepositoryMetadata(this.repository.id.toString());
      } catch (doesNotExist) {
        // ignore: 404 is standard here
      }
      let lockdownState = RepositoryLockdownState.Locked;
      if (action === 'created' && this.repository.fork && lockdownForks) {
        lockdownState = RepositoryLockdownState.AdministratorLocked;
        isForkAdministratorLocked = true;
        lockdownLog.push('The repository is a fork and will be administrator locked');
        if (upstreamLogin && this.operations.isManagedOrganization(upstreamLogin)) {
          lockdownLog.push(`The parent organization, ${upstreamLogin}, is also an organization managed by the company.`);
          isForkParentManagedBySystem = true;
        }
      }
      if (repositoryMetadata) {
        lockdownLog.push(`Repository metadata already exists for repository ID ${this.repository.id}`);
        const updateMetadata = this.populateRepositoryMetadata(repositoryMetadata, username, thirdPartyId, link, transferSourceRepositoryLogin);
        await this.repositoryMetadataProvider.updateRepositoryMetadata(updateMetadata);
        lockdownLog.push(`Updated the repository metadata with username and link information`);
      } else {
        repositoryMetadata = this.populateRepositoryMetadata(new RepositoryMetadataEntity(), username, thirdPartyId, link, transferSourceRepositoryLogin);
        repositoryMetadata.created = new Date();
        repositoryMetadata.lockdownState = lockdownState;
        repositoryMetadata.repositoryId = this.repository.id.toString();
        repositoryMetadata.repositoryName = this.repository.name;
        repositoryMetadata.organizationName = this.organization.name;
        repositoryMetadata.organizationId = this.organization.id.toString();
        repositoryMetadata.initialRepositoryDescription = this.repository.description;
        repositoryMetadata.initialRepositoryHomepage = this.repository.homepage;
        repositoryMetadata.initialRepositoryVisibility = this.repository.private ? GitHubRepositoryVisibility.Private : GitHubRepositoryVisibility.Public;
        await this.repositoryMetadataProvider.createRepositoryMetadata(repositoryMetadata);
        lockdownLog.push(`Created the initial repository metadata indicating the repo was created by ${username}`);
      }
    } catch (metadataSystemError) {
      console.dir(metadataSystemError);
      lockdownLog.push(`While writing repository metadata an error: ${metadataSystemError.message}`);
    }
    let patchChanges: IRepoPatch = {};
    if (!isForkAdministratorLocked && !isTransfer && !this.repository.private) {
      lockdownLog.push('Preparing to hide the public repository pending setup (V2)');
      patchChanges.private = true;
    }
    if (!isForkAdministratorLocked) {
      lockdownLog.push('Updating the description and web site to point at the setup wizard (V2)');
      lockdownLog.push(`Will direct the user to ${setupUrl}`);
      patchChanges.description = `${setupRepositorySubstring} ${setupUrl}`;
      patchChanges.homepage = setupUrl;
    }
    if (Object.getOwnPropertyNames(patchChanges).length > 0) {
      try {
        const descriptiveUpdate = Object.getOwnPropertyNames(patchChanges).map(key => {
          return `${key}=${patchChanges[key]}`
        }).join(', ');
        lockdownLog.push(`Updating repository with patch ${descriptiveUpdate}`);
        await this.repository.update(patchChanges);
      } catch (hideError) {
        lockdownLog.push(`Error while trying to update the new repo: ${hideError} (V2)`);
      }
    }
    try {
      await this.tryCreateReadme(this.repository, lockdownLog);
    } catch (readmeError) {
      lockdownLog.push(`Error with README updates: ${readmeError}`);
    }
    let mailSentToCreator = false;
    const operationsMails = [ this.operations.getRepositoriesNotificationMailAddress() ];
    const defaultAdministrativeUnlockUrl = `${this.repository.absoluteBaseUrl}administrativeLock`;
    const lockdownMailContent: IMailToLockdownRepo = {
      username,
      log: lockdownLog,
      organization: this.organization,
      repository: this.repository,
      linkToDeleteRepository: this.repository.absoluteBaseUrl + 'delete',
      linkToClassifyRepository: setupUrl,
      linkToAdministrativeUnlockRepository: companySpecific?.urls?.getAdministrativeUnlockUrl(this.repository) || defaultAdministrativeUnlockUrl,
      mailAddress: null,
      link,
      isForkAdministratorLocked,
    };
    lockdownLog.push(`The repo can be unlocked at ${lockdownMailContent.linkToClassifyRepository}`);
    const repoActionType = this.repository.fork ? 'forked' : (isTransfer ? 'transferred' : 'created');
    const stateVerb = isTransfer ? 'transferred' : 'new';
    const forkUnlockMail = this.operations.config.brand?.forkApprovalMail || this.operations.config.brand?.operationsMail;
    if (link) {
      try {
        const mailAddress = link.corporateMailAddress || await this.operations.getMailAddressFromCorporateUsername(link.corporateUsername);
        const repoName = this.repository.name;
        const subject = isForkAdministratorLocked ? `Your new fork requires administrator approval: ${repoName} (${username})` : `Please complete the setup of your ${stateVerb} GitHub repository ${repoName} (${username})`;
        if (mailAddress) {
          lockdownMailContent.mailAddress = mailAddress;
          const companyName = this.operations.config.brand.companyName;
          let managerInfo: ICachedEmployeeInformation = null;
          let reasonInfo = `This mail was sent to: ${mailAddress}`;
          try {
            const providers = this.operations.providers;
            let shouldTryNotifyManager = true;
            if (providers?.customizedNewRepositoryLogic) { // this is a hack around the new repo custom logic
              const customContext = providers.customizedNewRepositoryLogic.createContext({} /* "request" */);
              shouldTryNotifyManager = providers.customizedNewRepositoryLogic.shouldNotifyManager(customContext, link.corporateId);
            }
            if (shouldTryNotifyManager) {
              managerInfo = await this.operations.getCachedEmployeeManagementInformation(link.corporateId);
              if (managerInfo && managerInfo.managerMail) {
                reasonInfo += ` and manager ${managerInfo.managerMail}`;
              }
            }
          } catch (managerInfoError) {
            console.dir(managerInfoError);
          }
          const mailView = companySpecific?.views?.email?.repository?.newDirect || defaultMailTemplate;
          const mailToCreator: IMail = {
            to: mailAddress,
            subject,
            content: await this.operations.emailRender(mailView, {
              reason: (`You just ${repoActionType} a repository on GitHub and have additional actions required to gain access to continue to use it after classification.
                        ${reasonInfo}.`),
              headline: isForkAdministratorLocked ? 'Fork approval required' : `Setup your ${stateVerb} repository`,
              notification: isForkAdministratorLocked ? 'action' : 'information',
              app: `${companyName} GitHub`,
              isMailToCreator: true,
              lockdownMailContent,
              isForkAdministratorLocked,
              isForkParentManagedBySystem,
              upstreamLogin,
              linkToAdministrativeUnlockRepository: companySpecific?.urls?.getAdministrativeUnlockUrl(this.repository) || defaultAdministrativeUnlockUrl,
              action,
              forkUnlockMail,
              operationsMail: operationsMails.join(','),
              transferSourceRepositoryLogin,
            }),
          };
          if (managerInfo && managerInfo.managerMail) {
            mailToCreator.cc = managerInfo.managerMail;
          }
          await this.operations.sendMail(mailToCreator);
          lockdownLog.push(`sent an e-mail to the person who ${repoActionType} the repository ${mailAddress} (corporate username: ${link.corporateUsername})`);
          mailSentToCreator = true;
        } else {
          lockdownLog.push(`no e-mail address available for the corporate username ${link.corporateUsername}`);
        }
      } catch (noLinkOrEmail) {
        console.dir(noLinkOrEmail);
      }
    }
    if (operationsMails) {
      try {
        const subject = isForkAdministratorLocked ? `New fork ${this.organization.name}/${this.repository.name} requires approval - forked by ${username}` : `Repository ${repoActionType}: ${this.organization.name}/${this.repository.name} (by ${username})`;
        const mailToOperations: IMail = {
          to: operationsMails,
          subject,
          content: await this.operations.emailRender('newrepolockdown', {
            reason: (`A user just ${repoActionType} this repository directly on GitHub. As the operations contact for this system, you are receiving this e-mail.
                      This mail was sent to: ${operationsMails.join(', ')}`),
            headline: isForkAdministratorLocked ? `Fork ${this.organization.name}/${this.repository.name} by ${username}` : `Repo (${stateVerb}) ${this.organization.name}/${this.repository.name} ${repoActionType} by ${username}`,
            notification: 'information',
            app: `${this.operations.config.brand.companyName} GitHub`,
            isMailToOperations: true,
            lockdownMailContent,
            forkUnlockMail,
            transferSourceRepositoryLogin,
            action,
            mailSentToCreator,
            isForkAdministratorLocked,
          }),
        };
        await this.operations.sendMail(mailToOperations);
        lockdownLog.push(`sent an e-mail to the operations contact(s): ${operationsMails.join(', ')}`);
      } catch (mailIssue) {
        console.dir(mailIssue);
      }
    }
    const insights = this.operations.insights;
    if (insights) {
      insights.trackMetric({ name: 'LockedRepos', value: 1 });
      let metricName = isForkAdministratorLocked ? 'LockedForks' : 'LockedDirectRepos';
      if (isTransfer) {
        metricName = 'LockedTransfers';
      }
      insights.trackMetric({ name: metricName, value: 1 });
    }
    console.dir(lockdownLog);
    return true;
  }