@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
1 lines • 22 kB
Source Map (JSON)
{"version":3,"sources":["../../../packages/core/security/connection-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,MAAM,CAAC;AAGlD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG/D,OAAO,EAAE,cAAc,EAAe,MAAM,yBAAyB,CAAC;AAEtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAKrE,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAqB,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;OAEG;IACH,UAAU,EAAE,UAAU,CAAC;IAEvB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAE9B;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,wBAAwB,CAAC;IAE/B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,oBAAY,wBAAwB;IAChC;;OAEG;IACH,MAAM,IAAI;IAEV;;OAEG;IACH,OAAO,IAAI;IAEX;;OAEG;IACH,YAAY,IAAI;IAEhB;;OAEG;IACH,KAAK,IAAI;IAET;;OAEG;IACH,KAAK,IAAI;IAET;;OAEG;IACH,OAAO,IAAI;IAEX;;OAEG;IACH,SAAS,IAAI;CAChB;AAED,MAAM,WAAW,sBAAuB,SAAQ,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC;IAClE,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IAYrB,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,oBAAoB;IAC5B,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,oBAAoB;IAC5B,OAAO,CAAC,eAAe;IAjB3B,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,iBAAiB,CAA8D;IACvF,OAAO,CAAC,mBAAmB,CAAiB;IAE5C;;;;;OAKG;gBAES,GAAG,EAAE,GAAG,EACR,iBAAiB,EAAE,iBAAiB,EACpC,oBAAoB,EAAE,oBAAoB,EAC1C,iBAAiB,EAAE,iBAAiB,EACpC,cAAc,EAAE,cAAc,EAC9B,oBAAoB,EAAE,oBAAoB,EAC1C,eAAe,EAAE,oBAAoB;IASjD;;;OAGG;IACI,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC,cAAc,CAAC;IA6D5E;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAmH3B;;;;OAIG;IACH,OAAO,CAAC,iCAAiC;IAwBzC;;;;OAIG;IACH,OAAO,CAAC,iCAAiC;IA0BzC;;OAEG;IACH,OAAO,CAAC,8BAA8B;IAgBtC;;OAEG;IACH,OAAO,CAAC,6BAA6B;CAexC","file":"connection-stream.d.ts","sourcesContent":["import { Observable, of, throwError } from 'rxjs';\r\nimport { AjaxError } from 'rxjs/ajax';\r\nimport { catchError, map, tap } from 'rxjs/operators';\r\nimport { ExtensionBrokerQuery } from '../data/extension-broker/extension-broker';\r\nimport { GatewayConnection } from '../data/gateway-connection';\r\nimport { HttpStatusCode } from '../data/http-constants';\r\nimport { Net } from '../data/net';\r\nimport { NodeConnection, NodeRequest } from '../data/node-connection';\r\nimport { PowerShellCommand } from '../data/powershell';\r\nimport { PowerShellConnection } from '../data/powershell-connection';\r\nimport { LogLevel } from '../diagnostics/log-level';\r\nimport { Logging } from '../diagnostics/logging';\r\nimport { Strings } from '../generated/strings';\r\nimport { EnvironmentModule, EnvironmentModuleConnectionStatusProvider, ExtensionMethodIdentifier } from '../manifest/environment-modules';\r\nimport { Rpc } from '../rpc/rpc';\r\nimport { AuthorizationManager } from './authorization-manager';\r\nimport { Connection, ConnectionUtility } from './connection';\r\nimport { ConnectionManager } from './connection-manager';\r\n\r\n/**\r\n * Live connection interface.\r\n */\r\nexport interface LiveConnection {\r\n /**\r\n * the connection object.\r\n */\r\n connection: Connection;\r\n\r\n /**\r\n * the loading state while query the connection status.\r\n */\r\n loading: boolean;\r\n\r\n /**\r\n * the date number (Date.now() value).\r\n */\r\n lastUpdated: number;\r\n\r\n /**\r\n * The PowerShell connection.\r\n */\r\n isPowerShell: boolean;\r\n\r\n /**\r\n * The endpoint of PowerShell.\r\n */\r\n powerShellEndpoint: string;\r\n\r\n /**\r\n * the status of connection.\r\n */\r\n status?: LiveConnectionStatus;\r\n\r\n /**\r\n * The extra properties on the connection.\r\n */\r\n properties?: MsftSme.StringMap<any>;\r\n}\r\n\r\n/**\r\n * The live connection status.\r\n */\r\nexport interface LiveConnectionStatus {\r\n /**\r\n * The display string of status.\r\n */\r\n label?: string;\r\n\r\n /**\r\n * The status type.\r\n */\r\n type: LiveConnectionStatusType;\r\n\r\n /**\r\n * The detail connection error message.\r\n */\r\n details?: string;\r\n}\r\n\r\n/**\r\n * The live connection status type.\r\n */\r\nexport enum LiveConnectionStatusType {\r\n /**\r\n * Online status.\r\n */\r\n Online = 0,\r\n\r\n /**\r\n * Warning status.\r\n */\r\n Warning = 1,\r\n\r\n /**\r\n * Unauthorized status.\r\n */\r\n Unauthorized = 2,\r\n\r\n /**\r\n * Error status.\r\n */\r\n Error = 3,\r\n\r\n /**\r\n * Fatal status.\r\n */\r\n Fatal = 4,\r\n\r\n /**\r\n * Unknown status (used for loading status).\r\n */\r\n Unknown = 5,\r\n\r\n /**\r\n * Forbidden status.\r\n */\r\n Forbidden = 6\r\n}\r\n\r\nexport interface ConnectionStatusResult extends MsftSme.StringMap<any> {\r\n status?: LiveConnectionStatus;\r\n aliases?: string[];\r\n}\r\n\r\n/**\r\n * ConnectionStream class that enables to get all connections once and listen to the change.\r\n *\r\n * TODO:\r\n * 1. Support live connection status for a single connection in such a way that one could subscribe to it from ActiveConnection\r\n * with that observable always being for the active connection.\r\n * 2. Support updating all connection status on an interval. (using existing expiration field in cache)\r\n * 3. Support preserving status across sessions during the interval time.\r\n * currently we are using session storage, this may also require credentials to be preserved across sessions.\r\n */\r\nexport class ConnectionStream {\r\n private statusLabelMap: { [index: number]: string } = {};\r\n private connectionStrings = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Connection;\r\n private cacheLiveConnection: LiveConnection;\r\n\r\n /**\r\n * Initializes a new instance of the ConnectionStream class.\r\n * @param connectionManager the connection manager object.\r\n * @param powershellConnection the powerShell connection object.\r\n * @param gatewayConnection the gateway connection object.\r\n */\r\n constructor(\r\n private rpc: Rpc,\r\n private connectionManager: ConnectionManager,\r\n private powershellConnection: PowerShellConnection,\r\n private gatewayConnection: GatewayConnection,\r\n private nodeConnection: NodeConnection,\r\n private authorizationManager: AuthorizationManager,\r\n private extensionBroker: ExtensionBrokerQuery) {\r\n this.statusLabelMap[LiveConnectionStatusType.Error] = this.connectionStrings.ErrorState.label;\r\n this.statusLabelMap[LiveConnectionStatusType.Fatal] = this.connectionStrings.FatalState.label;\r\n this.statusLabelMap[LiveConnectionStatusType.Online] = this.connectionStrings.OnlineState.label;\r\n this.statusLabelMap[LiveConnectionStatusType.Unauthorized] = this.connectionStrings.NeedsAuthorizationState.label;\r\n this.statusLabelMap[LiveConnectionStatusType.Unknown] = this.connectionStrings.UnknownState.label;\r\n this.statusLabelMap[LiveConnectionStatusType.Warning] = this.connectionStrings.WarningState.label;\r\n }\r\n\r\n /**\r\n * Wraps a connection in a live connection object by retrieving its current status\r\n * @param connection the connection object.\r\n */\r\n public getLiveConnection(connection: Connection): Observable<LiveConnection> {\r\n // get the connection types status provider\r\n const typeInfo = ConnectionUtility.getConnectionTypeInfo(connection);\r\n if (typeInfo && typeInfo.provider && typeInfo.provider.connectionStatusProvider) {\r\n const now = Date.now();\r\n const statusProvider = typeInfo.provider.connectionStatusProvider;\r\n\r\n if (statusProvider.skipStatusCheck) {\r\n return of(<LiveConnection>{\r\n connection: connection,\r\n loading: false,\r\n status: {\r\n type: LiveConnectionStatusType.Online\r\n },\r\n isPowerShell: false,\r\n powerShellEndpoint: null\r\n });\r\n }\r\n\r\n if (this.cacheLiveConnection\r\n && this.cacheLiveConnection.status.type === LiveConnectionStatusType.Online\r\n && this.cacheLiveConnection.connection\r\n && this.cacheLiveConnection.connection.name === connection.name\r\n && this.cacheLiveConnection.connection.type === connection.type\r\n && now - this.cacheLiveConnection.lastUpdated < 2000) {\r\n return of(this.cacheLiveConnection);\r\n } else {\r\n return this.getConnectionStatus(statusProvider, connection, connection.name)\r\n .pipe(\r\n map(data => {\r\n this.cacheLiveConnection = data;\r\n return data;\r\n }));\r\n }\r\n }\r\n\r\n // this should not happen, it means we have a malformed manifest or the user has uninstalled the relevant connection manager extension.\r\n const logMessage = this.connectionStrings.NoStatusProvider.message.format(connection.type);\r\n\r\n // log warning about this condition\r\n Logging.log({\r\n source: 'ConnectionStream',\r\n level: LogLevel.Warning,\r\n message: logMessage\r\n });\r\n\r\n // we dont need to fail, we can set this items status to unknown and continue\r\n const statusLabel = this.connectionStrings.NoStatusProvider.label;\r\n return of(<LiveConnection>{\r\n connection: connection,\r\n loading: false,\r\n status: {\r\n label: statusLabel,\r\n type: LiveConnectionStatusType.Unknown,\r\n details: logMessage\r\n },\r\n isPowerShell: false,\r\n powerShellEndpoint: null\r\n });\r\n }\r\n\r\n /**\r\n * Get connection status and aliases from statusProvider, retry alaises when connection nodeName NotFound\r\n * @param statusProvider status provider from typeInfo manifest\r\n * @param connection original connection\r\n * @param nodeName retry nodeName, can be connection.name or alias\r\n */\r\n private getConnectionStatus(\r\n statusProvider: EnvironmentModuleConnectionStatusProvider,\r\n connection: Connection,\r\n nodeName: string): Observable<LiveConnection> {\r\n // collect the status data from the status provider\r\n let statusAwaiter: Observable<ConnectionStatusResult>;\r\n if (statusProvider.powerShell) {\r\n statusAwaiter = this.getConnectionStatusFromPowershell(nodeName, statusProvider.powerShell);\r\n } else if (statusProvider.relativeGatewayUrl) {\r\n statusAwaiter = this.getConnectionStatusFromGatewayUrl(nodeName, statusProvider.relativeGatewayUrl);\r\n } else if (statusProvider.service) {\r\n statusAwaiter = this.getConnectionStatusFromService(nodeName, statusProvider.service);\r\n } else if (statusProvider.worker) {\r\n statusAwaiter = this.getConnectionStatusFromWorker(nodeName, statusProvider.worker);\r\n }\r\n\r\n return statusAwaiter\r\n .pipe(\r\n map(statusData => {\r\n // create connection object to return\r\n const liveConnection: LiveConnection = {\r\n connection: connection,\r\n loading: false,\r\n lastUpdated: Date.now(),\r\n isPowerShell: !!statusProvider.powerShell,\r\n powerShellEndpoint: statusProvider.powerShell && this.authorizationManager.getJeaEndpoint(connection.name)\r\n };\r\n // extract the status field from the data\r\n if (statusData.status) {\r\n liveConnection.status = statusData.status;\r\n delete statusData.status;\r\n } else {\r\n // if there is no status field, assume everything is good since we got this far.\r\n liveConnection.status = {\r\n type: LiveConnectionStatusType.Online\r\n };\r\n }\r\n // load localized values for label and details\r\n if (statusProvider.displayValueMap) {\r\n const label = statusProvider.displayValueMap[liveConnection.status.label];\r\n const details = statusProvider.displayValueMap[liveConnection.status.details];\r\n liveConnection.status.label = label || liveConnection.status.label;\r\n liveConnection.status.details = details || liveConnection.status.details;\r\n }\r\n // extract the aliases field from the data\r\n if (statusData.aliases) {\r\n this.connectionManager.saveAliasesData(statusData.aliases, connection, nodeName);\r\n delete statusData.aliases;\r\n }\r\n // any other properties returned by status call live in property bag.\r\n liveConnection.properties = statusData;\r\n\r\n // fill properties of connection with status data.\r\n connection.properties = connection.properties || {};\r\n Object.assign(connection.properties, statusData);\r\n\r\n // return the new live connection\r\n return liveConnection;\r\n }),\r\n catchError((error: AjaxError) => {\r\n const liveConnection = <LiveConnection>{\r\n connection: connection,\r\n loading: false,\r\n lastUpdated: Date.now(),\r\n isPowerShell: !!statusProvider.powerShell,\r\n powerShellEndpoint: statusProvider.powerShell && this.authorizationManager.getJeaEndpoint(connection.name)\r\n };\r\n if (Net.isUnauthorized(error)) {\r\n liveConnection.status = {\r\n type: LiveConnectionStatusType.Unauthorized\r\n };\r\n } else if (Net.isForbidden(error)) {\r\n liveConnection.status = {\r\n type: LiveConnectionStatusType.Forbidden\r\n };\r\n } else if (error.status === HttpStatusCode.NotFound || error.status === HttpStatusCode.BadRequest) {\r\n // failed connect to target node\r\n const alias = this.connectionManager.getActiveAlias(connection.name);\r\n // found alias, retry\r\n if (alias) {\r\n return this.getConnectionStatus(statusProvider, connection, alias);\r\n } else { // no alias, return error\r\n\r\n // For Badrequest(400), try to extract the PS error code.\r\n if (error.status === HttpStatusCode.BadRequest) {\r\n liveConnection.status = {\r\n type: LiveConnectionStatusType.Fatal,\r\n details: Net.getErrorMessage(error)\r\n };\r\n } else { // else just show the default connection failure error.\r\n const label = this.connectionStrings.NoConnection.label;\r\n const message = this.connectionStrings.NoConnection.message;\r\n liveConnection.status = {\r\n label: label,\r\n type: LiveConnectionStatusType.Unknown,\r\n details: message\r\n };\r\n }\r\n }\r\n } else {\r\n liveConnection.status = {\r\n type: LiveConnectionStatusType.Fatal,\r\n details: Net.getErrorMessage(error)\r\n };\r\n }\r\n return of(liveConnection);\r\n }),\r\n tap(liveConnection => {\r\n if (!liveConnection.status.label) {\r\n liveConnection.status.label = this.statusLabelMap[liveConnection.status.type]\r\n || this.statusLabelMap[LiveConnectionStatusType.Unknown];\r\n }\r\n }));\r\n }\r\n\r\n /**\r\n * Retrieves a connections status from a powershell status provider\r\n * @param nodeName the node name.\r\n * @param options The powershell options.\r\n */\r\n private getConnectionStatusFromPowershell(\r\n nodeName: string,\r\n options: PowerShellCommand): Observable<ConnectionStatusResult> {\r\n if (!nodeName) {\r\n // log warning about this condition\r\n const message = this.connectionStrings.ErrorNodeName.message;\r\n Logging.log({\r\n source: 'ConnectionStream',\r\n level: LogLevel.Error,\r\n message: message.format(nodeName)\r\n });\r\n return throwError(() => new Error(message));\r\n }\r\n\r\n const psSession = this.powershellConnection.createAutomaticSession(nodeName, { noAuth: true });\r\n return this.powershellConnection.run(psSession, options.command ? options : options.script)\r\n .pipe(\r\n map(response => {\r\n // we expect the script to return an object as the first result.\r\n // The object should have some property called 'status' but we will check this later.\r\n return response.results[0];\r\n }));\r\n }\r\n\r\n /**\r\n * Retrieves a connections status from a gatewayUrl status provider\r\n * @param nodeName the node name.\r\n * @param relativeUrl the relative url from the relativeGatewayUrl provider.\r\n */\r\n private getConnectionStatusFromGatewayUrl(nodeName: string, relativeUrl: string): Observable<ConnectionStatusResult> {\r\n if (!relativeUrl) {\r\n // log warning about this condition\r\n const message = this.connectionStrings.ErrorGatewayUrl.message;\r\n Logging.logError('ConnectionStream', message);\r\n return throwError(() => new Error(message));\r\n }\r\n\r\n relativeUrl = relativeUrl.replace('$connectionName', nodeName);\r\n\r\n // scan GWv2 API and use nodeConnection API so it can include authorization information.\r\n // /Services/LinuxBase/ssh/nodes/$connectionName/connectionstatus\r\n const segments = MsftSme.trimStart(relativeUrl, '/').split('/');\r\n if (segments.length > 4 && segments[0].localeCompareIgnoreCase('services') === 0) {\r\n segments.shift();\r\n const serviceName = segments.shift();\r\n const controllerName = segments.shift();\r\n segments.shift();\r\n segments.shift();\r\n const remained = segments.join('/');\r\n return this.nodeConnection.get({ serviceName, controllerName, nodeName }, remained, <NodeRequest>{ noAuth: true });\r\n }\r\n\r\n return this.gatewayConnection.get(relativeUrl);\r\n }\r\n\r\n /**\r\n * Retrieves a connections status from a extension service method\r\n */\r\n private getConnectionStatusFromService(nodeName: string, methodId: ExtensionMethodIdentifier): Observable<ConnectionStatusResult> {\r\n if (!methodId) {\r\n // log warning about this condition\r\n const message = this.connectionStrings.ErrorService.message;\r\n Logging.log({\r\n source: 'ConnectionStream',\r\n level: LogLevel.Error,\r\n message: message\r\n });\r\n return throwError(() => new Error(message));\r\n }\r\n\r\n const entryPointId = EnvironmentModule.createEntrypointId(methodId.module, methodId.name);\r\n return this.extensionBroker.callService(entryPointId, methodId.method, methodId.version, nodeName);\r\n }\r\n\r\n /**\r\n * Retrieves a connections status from a extension worker method\r\n */\r\n private getConnectionStatusFromWorker(nodeName: string, methodId: ExtensionMethodIdentifier): Observable<ConnectionStatusResult> {\r\n if (!methodId) {\r\n // log warning about this condition\r\n const message = this.connectionStrings.ErrorWorker.message;\r\n Logging.log({\r\n source: 'ConnectionStream',\r\n level: LogLevel.Error,\r\n message: message\r\n });\r\n return throwError(() => new Error(message));\r\n }\r\n\r\n const entryPointId = EnvironmentModule.createEntrypointId(methodId.module, methodId.name);\r\n return this.extensionBroker.runWorker(entryPointId, methodId.method, methodId.version, nodeName);\r\n }\r\n}\r\n"]}