diff --git a/CHANGELOG.md b/CHANGELOG.md index e099bf5b..2293e5ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. +## [5.0.0](https://github.com/Netflix-Skunkworks/stethoscope-app/tree/v5.0.0) + +Fix auto update issue, caused by undocumented changes in Electron + +### Changed +- stethoscope://update deep link now forces update without prompting + +## [4.0.2](https://github.com/Netflix-Skunkworks/stethoscope-app/tree/v4.0.2) + +Bug fixes + +### Changed +- Config files are loaded directly by React app now, removing XHR requests. +- Ensure window is not destroyed before autoscanning + ## [4.0.1](https://github.com/Netflix-Skunkworks/stethoscope-app/tree/v4.0.1) ### Added diff --git a/package.json b/package.json index 7f2d4186..35bb5a05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Stethoscope", - "version": "4.0.1", + "version": "5.0.0", "private": true, "homepage": "./", "author": "Netflix", @@ -67,6 +67,7 @@ "extend": "^3.0.2", "fast-glob": "^2.2.6", "generic-pool": "^3.4.2", + "glob": "^7.1.6", "graphql": "^14.3.1", "graphql-server-express": "^1.2.0", "graphql-tools": "^2.12.0", @@ -150,11 +151,15 @@ "gatekeeperAssess": false, "hardenedRuntime": true, "entitlements": "entitlements.mac.plist", - "entitlementsInherit": "entitlements.mac.plist" + "entitlementsInherit": "entitlements.mac.plist", + "extraResources": [ + "src/practices" + ] }, "win": { "target": "nsis", "extraResources": [ + "src/practices", "bitlocker-status/bitlocker-status.exe", "bitlocker-status/bitlocker-status.exe.confg", "bitlocker-status/bitlocker-status.pdb", @@ -173,7 +178,10 @@ }, "linux": { "target": "AppImage", - "category": "System" + "category": "System", + "extraResources": [ + "src/practices" + ] }, "protocols": [ { diff --git a/src/App.js b/src/App.js index 807088d3..fadec790 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -/* global fetch, Notification */ +/* global Notification */ import React, { Component } from 'react' import Stethoscope from './lib/Stethoscope' import Device from './Device' @@ -13,11 +13,16 @@ import { HOST } from './constants' import appConfig from './config.json' import pkg from '../package.json' import ErrorMessage from './ErrorMessage' +import yaml from 'js-yaml' import './App.css' const socket = openSocket(HOST) + // CRA doesn't like importing native node modules // have to use window.require AFAICT const os = window.require('os') +const glob = window.require('glob') +const { readFileSync } = window.require('fs') +const path = window.require('path') const { shell, remote, ipcRenderer } = window.require('electron') const settings = window.require('electron-settings') const log = remote.getGlobal('log') @@ -53,7 +58,11 @@ class App extends Component { this.setState({ recentHang: settings.get('recentHang', 0) > 1 }) ipcRenderer.send('scan:init') // perform the initial policy load & scan - await this.loadPractices() + try { + await this.loadPractices() + } catch (e) { + console.error('Unable to load practices') + } // flag ensures the download:start event isn't sent multiple times this.downloadStartSent = false // handle context menu @@ -63,7 +72,8 @@ class App extends Component { // handles any errors that occur when updating (restores window size, etc.) ipcRenderer.on('download:error', this.onDownloadError) // trigger scan from main process - ipcRenderer.on('autoscan:start', ({ notificationOnViolation = false }) => { + ipcRenderer.on('autoscan:start', (args = {}) => { + // const { notificationOnViolation = false } = args if (!this.state.scanIsRunning) { ipcRenderer.send('scan:init') if (Object.keys(this.state.policy).length) { @@ -196,30 +206,33 @@ class App extends Component { * using them */ loadPractices = () => { - this.setState({ loading: true }, () => { - const files = ['config', 'policy', 'instructions'] - const promises = files.map(item => - fetch(`${HOST}/${item}`) - .then(async res => { - if (!res.ok) { - log.error(`Unable to locate ${item}`) - const response = await res.json() - throw new Error(response.error || `Unable to locate ${item}`) + return new Promise((resolve, reject) => + this.setState({ loading: true }, () => { + const process = remote.process + const dev = process.env.STETHOSCOPE_ENV === 'development' + const basePath = `${dev ? '.' : process.resourcesPath}/src/practices` + + glob(`${basePath}/*.yaml`, (err, files) => { + if (err || !files.length) { + reject(err) + } + const configs = {} + files.forEach(filePath => { + const parts = path.parse(filePath) + const handle = readFileSync(filePath, 'utf8') + configs[parts.name.split('.').shift()] = yaml.safeLoad(handle) + }) + + this.setState({ ...configs, loading: false }, () => { + if (!this.state.scanIsRunning) { + this.handleScan() } - return res }) - .then(res => res.json()) - .catch(this.handleErrorYAML) - ) - Promise.all(promises).then(([config, policy, instructions]) => { - this.setState({ config, policy, instructions }, () => { - if (!this.state.scanIsRunning) { - this.handleScan() - } + resolve() }) - }).catch(this.handleErrorGraphQL) - }) + }) + ) } /** @@ -322,33 +335,37 @@ class App extends Component { // assume no errors and loaded state if (!content) { const args = [policy, result, device, instructions.practices, platform] - const secInfo = Stethoscope.partitionSecurityInfo(...args) - const decoratedDevice = Object.assign({}, device, secInfo, { lastScanTime }) - const lastScanFriendly = moment(lastScanTime).fromNow() - - content = ( -
- -
- ) + try { + const secInfo = Stethoscope.partitionSecurityInfo(...args) + const decoratedDevice = Object.assign({}, device, secInfo, { lastScanTime }) + const lastScanFriendly = moment(lastScanTime).fromNow() + + content = ( +
+ +
+ ) + } catch (e) { + throw new Error(`Unable to partition data: ${e.message}\n${args.join(', ')}`) + } } return ( diff --git a/src/Footer.js b/src/Footer.js index 29fb63a5..23fd78bc 100644 --- a/src/Footer.js +++ b/src/Footer.js @@ -17,7 +17,7 @@ export default function Footer (props) { onClick={onRescan} > - {instructions.strings.rescanButton} + {instructions && instructions.strings && instructions.strings.rescanButton} {webScopeLink && (