UNPKG

@broadcom/endevor-bridge-for-git-for-zowe-cli

Version:

Endevor Bridge for Git plug-in for Zowe CLI

163 lines (158 loc) 8.44 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var BaseHandler = require('../../../BaseHandler.js'); require('path'); require('../../../../api/doc/ebg/IMappingView.js'); var EBGUserService = require('../../../../api/service/EBGUserService.js'); var imperative = require('@zowe/imperative'); var LocalRepositoryUtils = require('../../../../api/utils/LocalRepositoryUtils.js'); require('../../../../api/utils/ChangeValidator.js'); require('../../../../api/doc/IBranchMetadata.js'); require('lodash'); var EBGSession = require('../../../sessions/EBGSession.js'); var BridgeMetadataUtils = require('../../../../api/utils/BridgeMetadataUtils.js'); var HookFingerprintUtils = require('../../../../api/utils/HookFingerprintUtils.js'); var ConfigureRepositoryOptions = require('../../../options/ConfigureRepositoryOptions.js'); var prompts = require('@inquirer/prompts'); var EBGConfigService = require('../../../../api/service/EBGConfigService.js'); /* * Copyright (c) 2026 Broadcom. All Rights Reserved. The term * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * * This software and all information contained therein is * confidential and proprietary and shall not be duplicated, * used, disclosed, or disseminated in any way except as * authorized by the applicable license agreement, without the * express written permission of Broadcom. All authorized * reproductions must be marked with this language. * * EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO * THE EXTENT PERMITTED BY APPLICABLE LAW, BROADCOM PROVIDES THIS * SOFTWARE WITHOUT WARRANTY OF ANY KIND, INCLUDING WITHOUT * LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL BROADCOM * BE LIABLE TO THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR * DAMAGE, DIRECT OR INDIRECT, FROM THE USE OF THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, LOST PROFITS, BUSINESS * INTERRUPTION, GOODWILL, OR LOST DATA, EVEN IF BROADCOM IS * EXPRESSLY ADVISED OF SUCH LOSS OR DAMAGE. * */ class ConfigureRepositoryHandler extends BaseHandler.BaseHandler { async processWithSession() { this.checkMappingConfiguration(); this.checkSecureConnection(); await this.checkExistenceOfFiles(); const [newToken, prepushScript] = await Promise.all([ EBGUserService.EBGUserService.generatePrePushHookToken(this.apiClient), EBGConfigService.EBGConfigService.getPrepushHook(this.apiClient), ]); await this.checkPrepushHookFingerprint(prepushScript); const task = { percentComplete: imperative.TaskProgress.ZERO_PERCENT, statusMessage: "Installing pre-push hook", stageName: imperative.TaskStage.IN_PROGRESS, }; this.progress.startBar({ task }); const mergeDriverResult = BridgeMetadataUtils.BridgeMetadataUtils.configureMergeDriver(); if (mergeDriverResult.error || mergeDriverResult.status !== 0) { this.console.log(`WARNING: Setting automatic merge drive failed. Run "${BridgeMetadataUtils.BridgeMetadataUtils.GIT_BIN} ${BridgeMetadataUtils.BridgeMetadataUtils.GIT_MERGE_ARGS.join(" ")}" manually!`); } BridgeMetadataUtils.BridgeMetadataUtils.writeTokenToBfgSetup(newToken.localHookToken ?? ""); BridgeMetadataUtils.BridgeMetadataUtils.writeGitPrepushHook(prepushScript); task.stageName = imperative.TaskStage.COMPLETE; this.console.log("Pre-push hook successfully installed"); } async checkPrepushHookFingerprint(script) { const serverUrl = this.getServerUrl(); const newHash = HookFingerprintUtils.computeHash(script); const storedHash = HookFingerprintUtils.readFingerprint(serverUrl, "."); if (storedHash === undefined) { this.console.log(`Pre-push hook script received from ${serverUrl}:\n${script}`); this.tryWriteFingerprint(serverUrl, newHash); return; } if (storedHash === newHash) { return; } this.console.log(`New pre-push hook script received from ${serverUrl}:\n${script}`); const accepted = await prompts.confirm({ message: `The pre-push hook script from ${serverUrl} has changed since it was last installed.\n` + ` Previous: ${storedHash.substring(0, 16)}...\n` + ` Current: ${newHash.substring(0, 16)}...\n` + `If the EBG server was recently updated this is expected. Accept the new script?`, default: false, }); if (!accepted) { throw new imperative.ImperativeError({ msg: "Pre-push hook installation aborted: the script change was not accepted.", }); } this.tryWriteFingerprint(serverUrl, newHash); } tryWriteFingerprint(serverUrl, hash) { try { HookFingerprintUtils.writeFingerprint(serverUrl, hash, "."); } catch (err) { this.console.log(`WARNING: Could not save hook fingerprint: ${err.message}.\n` + `Script change detection will not work on future installs. ` + `Ensure the Zowe home directory is writable.`); } } getServerUrl() { const protocol = this.getOption(EBGSession.EBGSession.EBG_OPTION_PROTOCOL, true); const port = this.getOption(EBGSession.EBGSession.EBG_OPTION_PORT, true); const host = this.getOption(EBGSession.EBGSession.EBG_OPTION_HOST, true); const basePath = this.getOption(EBGSession.EBGSession.EBG_OPTION_BASE_PATH, false) ?? "/"; return new URL(basePath, `${protocol}://${host}:${port}`).toString(); } checkSecureConnection() { const rejectUnauthorized = this.getOption(EBGSession.EBGSession.EBG_OPTION_REJECT_UNAUTHORIZED, false); const protocol = this.getOption(EBGSession.EBGSession.EBG_OPTION_PROTOCOL, true); if (!rejectUnauthorized || protocol === "http") { throw new imperative.ImperativeError({ msg: "Cannot install the pre-push hook: the connection to the EBG server is not secure.\n" + "The hook script is fetched from the server and installed as an executable at " + "'.git/hooks/pre-push', where it runs automatically on every 'git push'. " + "Delivering it over an unauthenticated channel would allow an on-path attacker " + "to substitute a malicious payload.\n" + "Connect using HTTPS with certificate validation enabled. " + "If you are using '--reject-unauthorized false', remove that flag or set it to 'true'.", }); } } checkMappingConfiguration() { const mappingUrl = new URL(LocalRepositoryUtils.LocalRepositoryUtils.readMappingMetadata(".").url); this.validateRequiredOptions(); const profileUrl = new URL(this.getServerUrl()); if (mappingUrl.toString() !== profileUrl.toString()) { throw new imperative.ImperativeError({ msg: `The mapping is linked to different server.`, additionalDetails: `Mapping URL: ${mappingUrl.toString()}\nServer URL: ${profileUrl.toString()}`, }); } } async checkExistenceOfFiles() { const force = this.getOption(ConfigureRepositoryOptions.ConfigureRepositoryOptions.FORCE_REWRITE, false); if (!force && BridgeMetadataUtils.BridgeMetadataUtils.checkBfgSetupExists()) { const shouldOverwrite = await prompts.confirm({ message: "Setup file with token already exists. Do you want to overwrite it?", default: false, }); if (!shouldOverwrite) { throw new imperative.ImperativeError({ msg: "Setup file already exists" }); } } if (!force && BridgeMetadataUtils.BridgeMetadataUtils.checkGitPrepushHookExists()) { const shouldOverwrite = await prompts.confirm({ message: "Git pre-push hook already exists. Do you want to overwrite it?", default: false, }); if (!shouldOverwrite) { throw new imperative.ImperativeError({ msg: "Pre-push hook already exists" }); } } } } exports.default = ConfigureRepositoryHandler;