mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
REFACTOR: Consolidate checks under getFailureResult() (#2476)
There was duplicated code, and more importantly, were were handling certain things subtly differently in exec() and scp() as a result. This notably causes a behavior change in exec() and scp() where failure to authenticate now returns failure instead of throwing, which I believe is the proper response. This also makes it easier to see in the code exactly which functions require what (auth, session, etc.)
This commit is contained in:
@@ -19,6 +19,8 @@ type FailureResultOptions = {
|
|||||||
requireSession?: boolean;
|
requireSession?: boolean;
|
||||||
requireDirectConnection?: boolean;
|
requireDirectConnection?: boolean;
|
||||||
preventUseOnStationaryServers?: boolean;
|
preventUseOnStationaryServers?: boolean;
|
||||||
|
allowNonDarknet?: boolean;
|
||||||
|
backdoorBypasses?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logger = (ctx: NetscriptContext) => (message: string) => helpers.log(ctx, () => message);
|
export const logger = (ctx: NetscriptContext) => (message: string) => helpers.log(ctx, () => message);
|
||||||
@@ -39,13 +41,13 @@ export function getFailureResult(
|
|||||||
):
|
):
|
||||||
| { success: true; code: DarknetResponseCode; message: string; server: DarknetServer }
|
| { success: true; code: DarknetResponseCode; message: string; server: DarknetServer }
|
||||||
| { success: false; code: DarknetResponseCode; message: string } {
|
| { success: false; code: DarknetResponseCode; message: string } {
|
||||||
expectDarknetAccess(ctx);
|
|
||||||
const currentServer = ctx.workerScript.getServer();
|
const currentServer = ctx.workerScript.getServer();
|
||||||
const targetServer = GetServer(host);
|
const targetServer = GetServer(host);
|
||||||
// If the target server does not exist
|
|
||||||
if (!targetServer) {
|
if (!targetServer) {
|
||||||
if (DarknetState.offlineServers.includes(host)) {
|
if (DarknetState.offlineServers.includes(host)) {
|
||||||
// If the server is offline, return a dummy object with isOnline = false.
|
// Because servers going offline is timing-sensitive, it is outside of
|
||||||
|
// player's control. So we don't want to throw for "server does not exist" in this case,
|
||||||
|
// despite throwing being the usual doctrine.
|
||||||
logger(ctx)(`Server ${host} is offline.`);
|
logger(ctx)(`Server ${host} is offline.`);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -53,20 +55,42 @@ export function getFailureResult(
|
|||||||
message: GenericResponseMessage.ServiceUnavailable,
|
message: GenericResponseMessage.ServiceUnavailable,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Throw, otherwise.
|
|
||||||
throw errorMessage(ctx, `Server ${host} does not exist.`);
|
throw errorMessage(ctx, `Server ${host} does not exist.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const success = {
|
||||||
|
success: true,
|
||||||
|
code: ResponseCodeEnum.Success,
|
||||||
|
message: GenericResponseMessage.Success,
|
||||||
|
server: targetServer as DarknetServer,
|
||||||
|
} as const;
|
||||||
if (!(targetServer instanceof DarknetServer)) {
|
if (!(targetServer instanceof DarknetServer)) {
|
||||||
|
if (options.allowNonDarknet) {
|
||||||
|
// The return is off-shape here: server is of type DarknetServer, but
|
||||||
|
// we've explicitly validated that it's only a BaseServer. Callers
|
||||||
|
// using allowNonDarknet should call GetServer on their own for proper
|
||||||
|
// type-safety, instead of using the server field.
|
||||||
|
return success;
|
||||||
|
}
|
||||||
const result = `${targetServer.hostname} is not a darknet server.`;
|
const result = `${targetServer.hostname} is not a darknet server.`;
|
||||||
throw errorMessage(ctx, result);
|
throw errorMessage(ctx, result);
|
||||||
}
|
}
|
||||||
|
// This is down here because we don't require darknet access for using
|
||||||
|
// allowNonDarknet APIs on non-darknet servers.
|
||||||
|
expectDarknetAccess(ctx);
|
||||||
if (options.preventUseOnStationaryServers && targetServer.isStationary) {
|
if (options.preventUseOnStationaryServers && targetServer.isStationary) {
|
||||||
const result = `${targetServer.hostname} is not a valid target: it is a stationary server.`;
|
const result = `${targetServer.hostname} is not a valid target: it is a stationary server.`;
|
||||||
throw errorMessage(ctx, result);
|
throw errorMessage(ctx, result);
|
||||||
}
|
}
|
||||||
if (options.requireDirectConnection && !isDirectConnected(currentServer, targetServer)) {
|
if (
|
||||||
const result = `${targetServer.hostname} is not connected to the current server ${currentServer.hostname}. It may have moved.`;
|
options.requireDirectConnection &&
|
||||||
|
!isDirectConnected(currentServer, targetServer) &&
|
||||||
|
!(options.backdoorBypasses && targetServer.backdoorInstalled)
|
||||||
|
) {
|
||||||
|
let result = `${targetServer.hostname} is not connected to the current server ${currentServer.hostname}. It may have moved.`;
|
||||||
|
if (options.backdoorBypasses) {
|
||||||
|
result += " You can also use a backdoor or stasis link on the target to allow remote access.";
|
||||||
|
}
|
||||||
logger(ctx)(result);
|
logger(ctx)(result);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -74,7 +98,11 @@ export function getFailureResult(
|
|||||||
message: GenericResponseMessage.DirectConnectionRequired,
|
message: GenericResponseMessage.DirectConnectionRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if ((options.requireSession || options.requireAdminRights) && !targetServer.hasAdminRights) {
|
if (ctx.workerScript.hostname === targetServer.hostname || targetServer.hostname === SpecialServers.DarkWeb) {
|
||||||
|
// We always are authed to ourselves and DarkWeb. Early-out past the last checks.
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
if (options.requireAdminRights && !targetServer.hasAdminRights) {
|
||||||
const result = `${targetServer.hostname} requires root access. Use ns.dnet.authenticate() to gain access.`;
|
const result = `${targetServer.hostname} requires root access. Use ns.dnet.authenticate() to gain access.`;
|
||||||
logger(ctx)(result);
|
logger(ctx)(result);
|
||||||
return {
|
return {
|
||||||
@@ -97,12 +125,7 @@ export function getFailureResult(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return success;
|
||||||
success: true,
|
|
||||||
code: ResponseCodeEnum.Success,
|
|
||||||
message: GenericResponseMessage.Success,
|
|
||||||
server: targetServer,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isDirectConnected = (currentServer: BaseServer, targetServer: DarknetServer): boolean =>
|
export const isDirectConnected = (currentServer: BaseServer, targetServer: DarknetServer): boolean =>
|
||||||
@@ -128,39 +151,6 @@ export function expectRunningOnDarknetServer(ctx: NetscriptContext): DarknetServ
|
|||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expectAuthenticated(ctx: NetscriptContext, server: DarknetServer) {
|
|
||||||
/**
|
|
||||||
* Some non-dnet APIs (e.g., scp, exec) requires a session. We make darkweb an exception, so the player can interact
|
|
||||||
* with it without buying DarkscapeNavigator.exe.
|
|
||||||
*/
|
|
||||||
if (ctx.workerScript.hostname === server.hostname || server.hostname === SpecialServers.DarkWeb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!server.hasAdminRights) {
|
|
||||||
throw errorMessage(
|
|
||||||
ctx,
|
|
||||||
`[${ctx.function}] Server ${server.hostname} is password-protected. Use ns.dnet.authenticate() to gain access before running ${ctx.function}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!isAuthenticated(server, ctx.workerScript.pid)) {
|
|
||||||
throw errorMessage(
|
|
||||||
ctx,
|
|
||||||
`[${ctx.function}] Server ${server.hostname} requires a session to be targeted with ${ctx.function}. Use ns.dnet.connectToSession() first to authenticate with that server.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function checks if the target server has a session and a direct connection (serversOnNetwork, stasis link,
|
|
||||||
* backdoor) to the running script's server.
|
|
||||||
*/
|
|
||||||
export function hasExecConnection(ctx: NetscriptContext, targetServer: DarknetServer) {
|
|
||||||
expectAuthenticated(ctx, targetServer);
|
|
||||||
const directConnected = isDirectConnected(ctx.workerScript.getServer(), targetServer);
|
|
||||||
const backdoored = targetServer.backdoorInstalled;
|
|
||||||
return directConnected || backdoored;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTimeoutChance() {
|
export function getTimeoutChance() {
|
||||||
const backdooredDarknetServerCount = getBackdooredDarkwebServers().length - 2;
|
const backdooredDarknetServerCount = getBackdooredDarkwebServers().length - 2;
|
||||||
return Math.max(Math.min(backdooredDarknetServerCount * 0.03, 0.5), 0);
|
return Math.max(Math.min(backdooredDarknetServerCount * 0.03, 0.5), 0);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ import { compile } from "./NetscriptJSEvaluator";
|
|||||||
import { Script } from "./Script/Script";
|
import { Script } from "./Script/Script";
|
||||||
import { NetscriptFormat } from "./NetscriptFunctions/Format";
|
import { NetscriptFormat } from "./NetscriptFunctions/Format";
|
||||||
import { DarknetState } from "./DarkNet/models/DarknetState";
|
import { DarknetState } from "./DarkNet/models/DarknetState";
|
||||||
import { expectAuthenticated, hasExecConnection } from "./DarkNet/effects/offlineServerHandling";
|
import { getFailureResult } from "./DarkNet/effects/offlineServerHandling";
|
||||||
import { DarknetServer } from "./Server/DarknetServer";
|
import { DarknetServer } from "./Server/DarknetServer";
|
||||||
import { FragmentTypeEnum } from "./CotMG/FragmentType";
|
import { FragmentTypeEnum } from "./CotMG/FragmentType";
|
||||||
import { exampleDarknetServerData, ResponseCodeEnum } from "./DarkNet/Enums";
|
import { exampleDarknetServerData, ResponseCodeEnum } from "./DarkNet/Enums";
|
||||||
@@ -653,20 +653,18 @@ export const ns: InternalAPI<NSFull> = {
|
|||||||
const host = helpers.string(ctx, "host", _host);
|
const host = helpers.string(ctx, "host", _host);
|
||||||
const runOpts = helpers.runOptions(ctx, _thread_or_opt);
|
const runOpts = helpers.runOptions(ctx, _thread_or_opt);
|
||||||
const args = helpers.scriptArgs(ctx, _args);
|
const args = helpers.scriptArgs(ctx, _args);
|
||||||
if (DarknetState.offlineServers.includes(host)) {
|
if (
|
||||||
helpers.log(ctx, () => `Script execution failed, because ${host} is offline.`);
|
!getFailureResult(ctx, host, {
|
||||||
|
allowNonDarknet: true,
|
||||||
|
requireAdminRights: true,
|
||||||
|
requireSession: true,
|
||||||
|
requireDirectConnection: true,
|
||||||
|
backdoorBypasses: true,
|
||||||
|
}).success
|
||||||
|
) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const server = helpers.getServer(ctx, host);
|
const server = helpers.getServer(ctx, host);
|
||||||
if (server instanceof DarknetServer && !hasExecConnection(ctx, server)) {
|
|
||||||
const currentHostname = ctx.workerScript.getServer().hostname;
|
|
||||||
helpers.log(
|
|
||||||
ctx,
|
|
||||||
() =>
|
|
||||||
`The current server ${currentHostname} is not connected to ${host}. exec() to a password-protected server requires a direct connection, a stasis link, or a backdoor. Use exec() from an adjacent server, or set a stasis link on the target server.`,
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return runScriptFromScript("exec", server, path, args, ctx.workerScript, runOpts);
|
return runScriptFromScript("exec", server, path, args, ctx.workerScript, runOpts);
|
||||||
},
|
},
|
||||||
spawn:
|
spawn:
|
||||||
@@ -785,19 +783,20 @@ export const ns: InternalAPI<NSFull> = {
|
|||||||
scp: (ctx) => (_files, _destination, _source) => {
|
scp: (ctx) => (_files, _destination, _source) => {
|
||||||
const destination = helpers.string(ctx, "destination", _destination);
|
const destination = helpers.string(ctx, "destination", _destination);
|
||||||
const source = helpers.string(ctx, "source", _source ?? ctx.workerScript.hostname);
|
const source = helpers.string(ctx, "source", _source ?? ctx.workerScript.hostname);
|
||||||
if (DarknetState.offlineServers.includes(destination)) {
|
if (
|
||||||
helpers.log(ctx, () => `scp failed, because ${destination} is offline.`);
|
!getFailureResult(ctx, destination, {
|
||||||
|
allowNonDarknet: true,
|
||||||
|
requireAdminRights: true,
|
||||||
|
requireSession: true,
|
||||||
|
}).success
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (DarknetState.offlineServers.includes(source)) {
|
if (!getFailureResult(ctx, source, { allowNonDarknet: true }).success) {
|
||||||
helpers.log(ctx, () => `scp failed, because ${source} is offline.`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const sourceServer = helpers.getServer(ctx, source);
|
const sourceServer = helpers.getServer(ctx, source);
|
||||||
const destServer = helpers.getServer(ctx, destination);
|
const destServer = helpers.getServer(ctx, destination);
|
||||||
if (destServer instanceof DarknetServer) {
|
|
||||||
expectAuthenticated(ctx, destServer);
|
|
||||||
}
|
|
||||||
const files = Array.isArray(_files) ? _files : [_files];
|
const files = Array.isArray(_files) ? _files : [_files];
|
||||||
const lits: FilePath[] = [];
|
const lits: FilePath[] = [];
|
||||||
const contentFiles: ContentFilePath[] = [];
|
const contentFiles: ContentFilePath[] = [];
|
||||||
|
|||||||
@@ -273,21 +273,17 @@ describe("home", () => {
|
|||||||
|
|
||||||
// Cannot scp before authenticating
|
// Cannot scp before authenticating
|
||||||
expect(dnetServer.hasAdminRights).toStrictEqual(false);
|
expect(dnetServer.hasAdminRights).toStrictEqual(false);
|
||||||
expect(() => {
|
expect(ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home)).toStrictEqual(false);
|
||||||
ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home);
|
|
||||||
}).toThrow(`Server ${dnetServerHostname} is password-protected`);
|
|
||||||
expect(dnetServer.scripts.size).toStrictEqual(0);
|
expect(dnetServer.scripts.size).toStrictEqual(0);
|
||||||
// Cannot exec before authenticating
|
// Cannot exec from home because there is no direct connection
|
||||||
expect(() => {
|
expect(ns.exec(scriptPath, dnetServerHostname)).toStrictEqual(0);
|
||||||
ns.exec(scriptPath, dnetServerHostname);
|
|
||||||
}).toThrow(`Server ${dnetServerHostname} is password-protected`);
|
|
||||||
|
|
||||||
const { ws, ns: nsDarkWeb } = getWorkerScriptAndNS(SpecialServers.DarkWeb);
|
const { ws: wsDarkWeb, ns: nsDarkWeb } = getWorkerScriptAndNS(SpecialServers.DarkWeb);
|
||||||
// Authenticate from darkweb
|
// Authenticate from darkweb
|
||||||
expect((await nsDarkWeb.dnet.authenticate(dnetServerHostname, dnetServer.password)).success).toStrictEqual(true);
|
expect((await nsDarkWeb.dnet.authenticate(dnetServerHostname, dnetServer.password)).success).toStrictEqual(true);
|
||||||
expect(dnetServer.hasAdminRights).toStrictEqual(true);
|
expect(dnetServer.hasAdminRights).toStrictEqual(true);
|
||||||
// Check session created after successfully calling authenticate API
|
// Check session created after successfully calling authenticate API
|
||||||
expect(getServerState(dnetServerHostname).authenticatedPIDs.includes(ws.pid)).toStrictEqual(true);
|
expect(getServerState(dnetServerHostname).authenticatedPIDs.includes(wsDarkWeb.pid)).toStrictEqual(true);
|
||||||
// Write the test script to darkweb
|
// Write the test script to darkweb
|
||||||
nsDarkWeb.write(scriptPath, scriptContent);
|
nsDarkWeb.write(scriptPath, scriptContent);
|
||||||
// scp from darkweb
|
// scp from darkweb
|
||||||
@@ -300,14 +296,10 @@ describe("home", () => {
|
|||||||
dnetServer.scripts.clear();
|
dnetServer.scripts.clear();
|
||||||
|
|
||||||
// Cannot scp from home without a session
|
// Cannot scp from home without a session
|
||||||
expect(() => {
|
expect(ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home)).toStrictEqual(false);
|
||||||
ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home);
|
|
||||||
}).toThrow(`Server ${dnetServerHostname} requires a session`);
|
|
||||||
expect(dnetServer.scripts.size).toStrictEqual(0);
|
expect(dnetServer.scripts.size).toStrictEqual(0);
|
||||||
// Cannot exec from home without a session
|
// Cannot exec from home because there is no direct connection
|
||||||
expect(() => {
|
expect(ns.exec(scriptPath, dnetServerHostname)).toStrictEqual(0);
|
||||||
ns.exec(scriptPath, dnetServerHostname);
|
|
||||||
}).toThrow(`Server ${dnetServerHostname} requires a session`);
|
|
||||||
|
|
||||||
// Create a session from home to dnet server
|
// Create a session from home to dnet server
|
||||||
expect(ns.dnet.connectToSession(dnetServerHostname, dnetServer.password).success).toStrictEqual(true);
|
expect(ns.dnet.connectToSession(dnetServerHostname, dnetServer.password).success).toStrictEqual(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user