Merge remote-tracking branch 'upstream/master' into SEARCH-538

This commit is contained in:
Keerthi Niranjan 2017-12-27 15:56:39 +05:30
commit 81f14b4916
44 changed files with 580 additions and 97 deletions

32
PULL_REQUEST_TEMPLATE.md Normal file
View File

@ -0,0 +1,32 @@
## Description
A few sentences describing the overall goals of the pull request's commits. Describe the problem or feature in addition to a link to the [JIRA-ticket](https://perzoinc.atlassian.net/browse/JIRA-ticket)
## Approach
How does this change address the problem?
- #### Problem with the code:
- #### Fix:
## Learning
Describe the research stage. Put link to Confluence page if possible. Links to blog posts, patterns, libraries or addons used to solve this problem.
#### Blog Posts
- [Alice and Bob](https://en.wikipedia.org/wiki/Alice_and_Bob) Wikipage for the famous placeholder names in engineering literature.
## Related PRs
List related PRs against other branches:
branch | PR
------ | ------
other_pr_rc | [link]()
other_pr_dev | [link]()
## Open Questions if any and Todos
- [ ] Unit-Tests
- [ ] Documentation
- [ ] Automation-Tests
When solved, check the box and explain the answer.

View File

@ -2,6 +2,10 @@
# SymphonyElectron
## About:
SymphonyElectron is a desktop client of the Symphony Communication Platform built for macOS, Windows 10 and Windows 7.
## Project Goals:
Our goal is to improve the performance and development agility of Symphony's desktop wrapper and build a path to support other wrappers by:
@ -64,4 +68,6 @@ In order to achieve those goals Symphony is participating and working in close c
- Remote logging is enabled for local and production cases and are sent to the backend server via the remote objects
## Misc notes
If desiring to run against server without proper cert use cmd line option: --ignore-certificate-errors
- If desiring to run against server without proper cert use cmd line option: --ignore-certificate-errors
- To start additional instance with custom data directory (if you want seperate user) use cmd line options: --multiInstance --userDataPath=<path to data dir>
- if directory doesn't exist, it will be created

View File

@ -1,8 +1,9 @@
{
"url": "https://foundation-dev.symphony.com",
"minimizeOnClose" : false,
"minimizeOnClose" : true,
"launchOnStartup" : true,
"alwaysOnTop" : false,
"whitelistUrl": "*",
"notificationSettings": {
"position": "upper-right",
"display": ""
@ -11,5 +12,9 @@
"submitURL": "https://localhost:1127/post",
"companyName": "Symphony",
"uploadToServer": false
},
"customFlags": {
"authServerWhitelist": "",
"authNegotiateDelegateWhitelist": ""
}
}

View File

@ -18,6 +18,10 @@
<label for='image'>image url:</label>
<input type='text' id='image' value='https://avatars0.githubusercontent.com/u/13243259?v=4&s=460'/>
</p>
<p>
<label for='company'>company:</label>
<input type='text' id='company' value='Symphony'/>
</p>
<p>
<label for='flash'>flash:</label>
<input type='checkbox' id='flash'/>
@ -92,6 +96,7 @@
var shouldStick = document.getElementById('sticky').checked;
var color = document.getElementById('color').value;
var tag = document.getElementById('tag').value;
var company = document.getElementById('company').value;
num++;
@ -104,7 +109,8 @@
data: {
hello: 'hello word'
},
tag: tag
tag: tag,
company: company
});
notf.addEventListener('click', onclick);

View File

@ -238,7 +238,6 @@
}).catch(function (err) {
resultsEl.innerHTML = JSON.stringify(err);
});
resultsEl.innerHTML = "Merging and Encrypting index completed"
});
}).catch(function (err) {
resultsEl.innerHTML = 'Error: ' + err;

View File

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>17A405</string>
<string>17B48</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
@ -27,17 +27,17 @@
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A1004</string>
<string>9B55</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<string>17B41</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0901</string>
<string>0910</string>
<key>DTXcodeBuild</key>
<string>9A1004</string>
<string>9B55</string>
<key>InstallerSectionTitle</key>
<string>Pod Settings</string>
<key>NSHumanReadableCopyright</key>

View File

@ -6,7 +6,7 @@
<dict>
<key>Resources/Base.lproj/MyInstallerPane.nib</key>
<data>
TF/AqkGdS25ttnHMS1l76ES81/w=
GZWN47BPj6b6mD6MnSVH/Qn0m3s=
</data>
<key>Resources/InstallerSections.plist</key>
<data>
@ -37,11 +37,11 @@
<dict>
<key>hash</key>
<data>
TF/AqkGdS25ttnHMS1l76ES81/w=
GZWN47BPj6b6mD6MnSVH/Qn0m3s=
</data>
<key>hash2</key>
<data>
gxXMI4SoTYE7jYkP5QJ7i804TUXR4x8LGSh99n9qers=
cJ/kr1HEozYL0YeZriWDtnOL1NEd22vHzH5WGp1T3hk=
</data>
</dict>
<key>Resources/InstallerSections.plist</key>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13196" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13196"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -59,7 +59,7 @@
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XPe-yO-v9Y">
<rect key="frame" x="194" y="85" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" inset="2" id="uvu-EE-3sp">
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="uvu-EE-3sp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>

View File

@ -31,7 +31,7 @@
}
// Now, validate the url against a url regex
NSString *regex = @"^((?:http:\/\/)|(?:https:\/\/))(www.)?((?:[a-zA-Z0-9]+\.[a-z]{3})|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?))([\/a-zA-Z0-9\.]*)$";
NSString *regex = @"^(https:\/\/|http:\/\/)(www.)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?(\/[a-zA-Z0-9-_.+!*'(),;/?:@=&$]*)?$";
NSPredicate *podUrlTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
if ([podUrlTest evaluateWithObject:podUrl]) {
return YES;
@ -48,23 +48,23 @@
NSString *podUrl = [_podUrlTextBox stringValue];
// By default, set autoLaunchOnStart to true
// By default, set autoLaunchOnStart and minimizeOnClose to true
NSString *autoLaunchOnStart = @"true";
NSString *minimizeOnClose = @"true";
// If the checkbox is changed, set the auto launch value accordingly
if ([_autoLaunchCheckBox state] == 0) {
autoLaunchOnStart = @"false";
}
// By default, set minimizeOnClose and alwaysOnTop to false
NSString *minimizeOnClose = @"false";
NSString *alwaysOnTop = @"false\n";
// If the checkbox is changed, set the minimize on close value accordingly
if ([_minimizeOnCloseCheckBox state] == 1) {
minimizeOnClose = @"true";
if ([_minimizeOnCloseCheckBox state] == 0) {
minimizeOnClose = @"false";
}
// By default, set alwaysOnTop to false
NSString *alwaysOnTop = @"false\n";
// If the checkbox is changed, set the always on top value accordingly
if ([_alwaysOnTopCheckBox state] == 1) {
alwaysOnTop = @"true\n";

View File

@ -4,24 +4,40 @@
tempFilePath='/tmp/sym_settings.txt'
installPath="$2"
configPath="/Symphony.app/Contents/config/Symphony.config"
newPath=$installPath$configPath
newPath=${installPath}${configPath}
## Get Symphony Settings from the temp file ##
pod_url=$(sed -n '1p' $tempFilePath);
pod_url=$(sed -n '1p' ${tempFilePath});
minimize_on_close=$(sed -n '2p' '/tmp/sym_settings.txt');
launch_on_startup=$(sed -n '3p' '/tmp/sym_settings.txt');
always_on_top=$(sed -n '4p' '/tmp/sym_settings.txt');
if [ "$pod_url" == "" ]; then
pod_url="https://corporate.symphony.com"
fi
if [ "$minimize_on_close" == "" ]; then
minimize_on_close=true;
fi
if [ "$launch_on_startup" == "" ]; then
launch_on_startup=true;
fi
if [ "$always_on_top" == "" ]; then
always_on_top=false;
fi
## Replace the default settings with the user selected settings ##
sed -i "" -E "s#\"url\" ?: ?\".*\"#\"url\"\: \"$pod_url\"#g" $newPath
sed -i "" -E "s#\"minimizeOnClose\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"minimizeOnClose\":\ $minimize_on_close#g" $newPath
sed -i "" -E "s#\"alwaysOnTop\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"alwaysOnTop\":\ $always_on_top#g" $newPath
sed -i "" -E "s#\"launchOnStartup\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"launchOnStartup\":\ $launch_on_startup#g" $newPath
sed -i "" -E "s#\"url\" ?: ?\".*\"#\"url\"\: \"$pod_url\"#g" ${newPath}
sed -i "" -E "s#\"minimizeOnClose\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"minimizeOnClose\":\ $minimize_on_close#g" ${newPath}
sed -i "" -E "s#\"alwaysOnTop\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"alwaysOnTop\":\ $always_on_top#g" ${newPath}
sed -i "" -E "s#\"launchOnStartup\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"launchOnStartup\":\ $launch_on_startup#g" ${newPath}
## Remove the temp settings file created ##
rm -f $tempFilePath
rm -f ${tempFilePath}
## For launching symphony with sandbox enabled, create a shell script that is used as the launch point for the app
EXEC_PATH=$installPath/Symphony.app/Contents/MacOS
exec $EXEC_PATH/Symphony --install $newPath $launch_on_startup
chmod 755 $EXEC_PATH/Symphony
EXEC_PATH=${installPath}/Symphony.app/Contents/MacOS
exec ${EXEC_PATH}/Symphony --install ${newPath} ${launch_on_startup}
chmod 755 ${EXEC_PATH}/Symphony

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
sudo killall Symphony
sudo rm -rf /Applications/Symphony.app

View File

@ -450,6 +450,13 @@
<key>PATH_TYPE</key>
<integer>1</integer>
</dict>
<key>PREINSTALL_PATH</key>
<dict>
<key>PATH</key>
<string>preinstall.sh</string>
<key>PATH_TYPE</key>
<integer>1</integer>
</dict>
<key>RESOURCES</key>
<array/>
</dict>

View File

@ -24,6 +24,7 @@
<ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" Value="dialog" MultiBuildValue="DefaultBuild:Tabloid.jpg" Type="1" MsiKey="DialogBitmap"/>
<ROW Property="INVALID_POD_URL" Value="valid" Type="4"/>
<ROW Property="MINIMIZE_ON_CLOSE" Value="true"/>
<ROW Property="MINIMIZE_ON_CLOSE_LABEL" Value="false" Type="4"/>
<ROW Property="Manufacturer" Value="Symphony"/>
<ROW Property="POD_URL" Value="https://corporate.symphony.com" Type="4"/>
@ -552,7 +553,7 @@
<ROW Action="AI_TxtUpdaterRollback" Type="11521" Source="TxtUpdater.dll" Target="OnTxtUpdaterRollback" WithoutSeq="true"/>
<ROW Action="KillParagon" Type="1" Source="aicustact.dll" Target="StopProcess" Options="1" AdditionalSeq="AI_DATA_SETTER_2"/>
<ROW Action="KillRenderer" Type="1" Source="aicustact.dll" Target="StopProcess" Options="1" AdditionalSeq="AI_DATA_SETTER"/>
<ROW Action="PodUrlValidation" Type="37" Target="Script Text" TargetUnformatted="// First, check if the protocol is part of the url, if not, prepend it&#13;&#10;var prefix = &quot;https://&quot;;&#13;&#10;if (Session.Property(&quot;POD_URL&quot;).substr(0, prefix.length) !== prefix) {&#13;&#10;&#9;Session.Property(&quot;POD_URL&quot;) = prefix + Session.Property(&quot;POD_URL&quot;);&#13;&#10;}&#13;&#10;&#13;&#10;// Check if the entered pod url is valid&#13;&#10;var podUrlRE = /^((?:http:\/\/)|(?:https:\/\/))(www.)?((?:[a-zA-Z0-9]+\.[a-z]{3})|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?))([\/a-zA-Z0-9\.]*)$/;&#13;&#10;var podUrlTest = podUrlRE.test(Session.Property(&quot;POD_URL&quot;));&#13;&#10;if (!podUrlTest) {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;invalid&quot;;&#13;&#10;} else {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;valid&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// By default, we set all the values to false and change based on conditions&#13;&#10;Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;false&quot;; &#13;&#10;Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;false&quot;;&#13;&#10;Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;false&quot;;&#13;&#10;&#13;&#10;// If always on top is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;ALWAYS_ON_TOP&quot;) &amp;&amp; Session.Property(&quot;ALWAYS_ON_TOP&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If launch on startup is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) &amp;&amp; Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If minimise on close is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;AUTO_START&quot;) &amp;&amp; Session.Property(&quot;AUTO_START&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}" WithoutSeq="true"/>
<ROW Action="PodUrlValidation" Type="37" Target="Script Text" TargetUnformatted="// First, check if the protocol is part of the url, if not, prepend it&#13;&#10;var prefix1 = &quot;https://&quot;;&#13;&#10;var prefix2 = &quot;http://&quot;;&#13;&#10;if (Session.Property(&quot;POD_URL&quot;).substr(0, prefix1.length) !== prefix1 &amp;&amp; Session.Property(&quot;POD_URL&quot;).substr(0, prefix2.length) !== prefix2) {&#13;&#10; Session.Property(&quot;POD_URL&quot;) = prefix1 + Session.Property(&quot;POD_URL&quot;);&#13;&#10;}&#13;&#10;&#13;&#10;// Check if the entered pod url is valid&#13;&#10;var podUrlRE = /^(https:\/\/|http:\/\/)(www.)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?(\/[a-zA-Z0-9-_.+!*&apos;(),;/?:@=&amp;$]*)?$/;&#13;&#10;var podUrlTest = podUrlRE.test(Session.Property(&quot;POD_URL&quot;));&#13;&#10;if (!podUrlTest) {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;invalid&quot;;&#13;&#10;} else {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;valid&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// By default, we set all the values to false and change based on conditions&#13;&#10;Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;false&quot;; &#13;&#10;Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;false&quot;;&#13;&#10;Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;false&quot;;&#13;&#10;&#13;&#10;// If always on top is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;ALWAYS_ON_TOP&quot;) &amp;&amp; Session.Property(&quot;ALWAYS_ON_TOP&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If launch on startup is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) &amp;&amp; Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If minimise on close is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;AUTO_START&quot;) &amp;&amp; Session.Property(&quot;AUTO_START&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}" WithoutSeq="true"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[AI_UserProgramFiles][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>

View File

@ -26,6 +26,10 @@
font-size: .9em;
}
.credentials-error {
color: red;
}
form {
padding-top: 15px;
}
@ -60,6 +64,7 @@
<div class="container">
<span>Please provide your login credentials for:</span>
<span id="hostname" class="hostname">hostname</span>
<span id="credentialsError" class="credentials-error">Invalid user name/password</span>
<form id="basicAuth" name="Basic Auth" action="Login">
<table class="form">
<tbody>

View File

@ -7,6 +7,7 @@ const path = require('path');
const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const { isMac } = require('../utils/misc');
let basicAuthWindow;
@ -14,7 +15,7 @@ const local = {};
let windowConfig = {
width: 360,
height: 270,
height: isMac ? 270 : 295,
show: false,
modal: true,
autoHideMenuBar: true,
@ -45,14 +46,20 @@ function getTemplatePath() {
* Opens the basic auth window for authentication
* @param {String} windowName - name of the window upon which this window should show
* @param {String} hostname - name of the website that requires authentication
* @param {boolean} isValidCredentials - false if invalid username or password
* @param {Function} clearSettings
* @param {Function} callback
*/
function openBasicAuthWindow(windowName, hostname, callback) {
function openBasicAuthWindow(windowName, hostname, isValidCredentials, clearSettings, callback) {
// Register callback function
if (typeof callback === 'function') {
local.authCallback = callback;
}
// Register close function
if (typeof clearSettings === 'function') {
local.clearSettings = clearSettings;
}
// This prevents creating multiple instances of the
// basic auth window
@ -89,6 +96,7 @@ function openBasicAuthWindow(windowName, hostname, callback) {
basicAuthWindow.webContents.on('did-finish-load', () => {
basicAuthWindow.webContents.send('hostname', hostname);
basicAuthWindow.webContents.send('isValidCredentials', isValidCredentials);
});
basicAuthWindow.on('close', () => {
@ -103,14 +111,12 @@ function openBasicAuthWindow(windowName, hostname, callback) {
ipc.on('login', (event, args) => {
if (typeof args === 'object' && typeof local.authCallback === 'function') {
local.authCallback(args.username, args.password);
basicAuthWindow.close();
closeAuthWindow(false);
}
});
ipc.on('close-basic-auth', () => {
if (basicAuthWindow) {
basicAuthWindow.close();
}
closeAuthWindow(true);
});
/**
@ -120,6 +126,19 @@ function destroyWindow() {
basicAuthWindow = null;
}
/**
* Method to close the auth window
* @param {boolean} clearSettings - Whether to clear the auth settings
*/
function closeAuthWindow(clearSettings) {
if (clearSettings && typeof local.clearSettings === 'function') {
local.clearSettings();
}
if (basicAuthWindow) {
basicAuthWindow.close();
}
}
module.exports = {
openBasicAuthWindow: openBasicAuthWindow

View File

@ -52,4 +52,15 @@ ipc.on('hostname', (event, host) => {
if (hostname){
hostname.innerHTML = host || 'unknown';
}
});
/**
* Triggered if user credentials are invalid
*/
ipc.on('isValidCredentials', (event, isValidCredentials) => {
let credentialsError = document.getElementById('credentialsError');
if (credentialsError){
credentialsError.style.display = isValidCredentials ? 'none' : 'block'
}
});

View File

@ -382,6 +382,34 @@ function clearCachedConfigs() {
globalConfig = null;
}
function readConfigFileSync() {
let configPath;
let globalConfigFileName = path.join('config', configFileName);
if (isDevEnv) {
// for dev env, get config file from asar
configPath = path.join(app.getAppPath(), globalConfigFileName);
} else {
// for non-dev, config file is placed by installer relative to exe.
// this is so the config can be easily be changed post install.
let execPath = path.dirname(app.getPath('exe'));
// for mac exec is stored in subdir, for linux/windows config
// dir is in the same location.
configPath = path.join(execPath, isMac ? '..' : '', globalConfigFileName);
}
let data = fs.readFileSync(configPath);
try {
return JSON.parse(data);
} catch (err) {
console.log("parsing config failed", err);
}
return null;
}
module.exports = {
configFileName,
@ -395,6 +423,11 @@ module.exports = {
// items below here are only exported for testing, do NOT use!
saveUserConfig,
clearCachedConfigs
clearCachedConfigs,
readConfigFileSync,
// use only if you specifically need to read global config fields
getGlobalConfigField,
};

View File

@ -12,7 +12,8 @@
// renderer process, this will have to do. See github issue posted here to
// electron: https://github.com/electron/electron/issues/9312
const { ipcRenderer } = require('electron');
const { ipcRenderer, remote } = require('electron');
const { isWindowsOS } = require('../utils/misc');
let nextId = 0;
let includes = [].includes;
@ -52,6 +53,17 @@ function getSources(options, callback) {
};
}
if (isWindowsOS) {
/**
* Sets the captureWindow to false if Desktop composition
* is disabled otherwise true
*
* Setting captureWindow to false returns only screen sources
* @type {boolean}
*/
captureWindow = remote.systemPreferences.isAeroGlassEnabled();
}
id = getNextId();
ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, updatedOptions.thumbnailSize, id);

View File

@ -3,6 +3,8 @@
const electron = require('electron');
const basicAuth = require('../basicAuth');
let currentAuthURL;
let tries = 0;
/**
* Having a proxy or hosts that requires authentication will allow user to
@ -12,14 +14,33 @@ electron.app.on('login', (event, webContents, request, authInfo, callback) => {
event.preventDefault();
// This check is to determine whether the request is for the same
// host if so then increase the login tries from which we can
// display invalid credentials
if (currentAuthURL !== request.url) {
currentAuthURL = request.url;
} else {
tries++
}
// name of the host to display
let hostname = authInfo.host || authInfo.realm;
let browserWin = electron.BrowserWindow.fromWebContents(webContents);
let windowName = browserWin.winName || '';
/**
* Method that resets currentAuthURL and tries
* if user closes the auth window
*/
function clearSettings() {
callback();
currentAuthURL = '';
tries = 0;
}
/**
* Opens an electron modal window in which
* user can enter credentials fot the host
*/
basicAuth.openBasicAuthWindow(windowName, hostname, callback);
basicAuth.openBasicAuthWindow(windowName, hostname, tries === 0, clearSettings, callback);
});

View File

@ -11,7 +11,7 @@ const AutoLaunch = require('auto-launch');
const urlParser = require('url');
// Local Dependencies
const {getConfigField, updateUserConfigWin, updateUserConfigMac} = require('./config.js');
const {getConfigField, updateUserConfigWin, updateUserConfigMac, readConfigFileSync} = require('./config.js');
const {setCheckboxValues} = require('./menus/menuTemplate.js');
const { isMac, isDevEnv } = require('./utils/misc.js');
const protocolHandler = require('./protocolHandler');
@ -81,8 +81,10 @@ const shouldQuit = app.makeSingleInstance((argv) => {
processProtocolAction(argv);
});
// quit if another instance is already running, ignore for dev env
if (!isDevEnv && shouldQuit) {
let allowMultiInstance = getCmdLineArg(process.argv, '--multiInstance', true) || isDevEnv;
// quit if another instance is already running, ignore for dev env or if app was started with multiInstance flag
if (!allowMultiInstance && shouldQuit) {
app.quit();
}
@ -103,6 +105,39 @@ if (isMac) {
});
}
/**
* Sets chrome authentication flags in electron
*/
function setChromeFlags() {
log.send(logLevels.INFO, 'checking if we need to set custom chrome flags!');
// Read the config parameters synchronously
let config = readConfigFileSync();
// If we cannot find any config, just skip setting any flags
if (config && config !== null && config.customFlags) {
log.send(logLevels.INFO, 'Chrome flags config found!');
// If we cannot find the authServerWhitelist config, move on
if (config.customFlags.authServerWhitelist && config.customFlags.authServerWhitelist !== "") {
log.send(logLevels.INFO, 'Setting auth server whitelist flag');
app.commandLine.appendSwitch('auth-server-whitelist', config.customFlags.authServerWhitelist);
}
// If we cannot find the authNegotiateDelegateWhitelist config, move on
if (config.customFlags.authNegotiateDelegateWhitelist && config.customFlags.authNegotiateDelegateWhitelist !== "") {
log.send(logLevels.INFO, 'Setting auth negotiate delegate whitelist flag');
app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', config.customFlags.authNegotiateDelegateWhitelist);
}
}
}
// Set the chrome flags
setChromeFlags();
/**
* This method will be called when Electron has finished
* initialization and is ready to create browser windows.
@ -176,6 +211,12 @@ function setupThenOpenMainWindow() {
// allows installer to launch app and set appropriate global / user config params.
let hasInstallFlag = getCmdLineArg(process.argv, '--install', true);
let perUserInstall = getCmdLineArg(process.argv, '--peruser', true);
let customDataArg = getCmdLineArg(process.argv, '--userDataPath=', false);
if (customDataArg && customDataArg.split('=').length > 1) {
let customDataFolder = customDataArg.split('=')[1];
app.setPath('userData', customDataFolder);
}
if (!isMac && hasInstallFlag) {
getConfigField('launchOnStartup')
.then(setStartup)
@ -193,9 +234,8 @@ function setupThenOpenMainWindow() {
let launchOnStartup = process.argv[3];
// We wire this in via the post install script
// to get the config file path where the app is installed
let appGlobalConfigPath = process.argv[2];
setStartup(launchOnStartup)
.then(() => updateUserConfigMac(appGlobalConfigPath))
.then(updateUserConfigMac)
.then(app.quit)
.catch(app.quit);
return;
@ -214,18 +254,15 @@ function setupThenOpenMainWindow() {
* @returns {Promise}
*/
function setStartup(lStartup) {
return symphonyAutoLauncher.isEnabled()
.then(function(isEnabled) {
if (!isEnabled && lStartup) {
return symphonyAutoLauncher.enable();
}
if (isEnabled && !lStartup) {
return symphonyAutoLauncher.disable();
}
return true;
});
return new Promise((resolve) => {
let launchOnStartup = (lStartup === 'true');
if (launchOnStartup) {
symphonyAutoLauncher.enable();
return resolve();
}
symphonyAutoLauncher.disable();
return resolve();
});
}
/**

View File

@ -65,7 +65,23 @@ const template = [{
}
},
{
label: 'Open Crashes Directory',
label: 'Set Downloads Directory',
click() {
electron.dialog.showOpenDialog({
title: 'Select Downloads Directory',
buttonLabel: 'Select',
properties: ['openDirectory', 'createDirectory']
}, (filePaths) => {
if (!filePaths || !Array.isArray(filePaths) || filePaths.length < 1) {
return;
}
updateConfigField('downloadsDirectory', filePaths[0]);
eventEmitter.emit('setDownloadsDirectory', filePaths[0]);
});
}
},
{
label: 'Open Crashes Directory',
click() {
const crashesDirectory = electron.crashReporter.getCrashesDirectory() + '/completed';
electron.shell.showItemInFolder(crashesDirectory);

View File

@ -23,7 +23,7 @@ function setStyle(config) {
let image = notiDoc.getElementById('image');
let logo = notiDoc.getElementById('symphony-logo');
let title = notiDoc.getElementById('title');
let pod = notiDoc.getElementById('pod');
let company = notiDoc.getElementById('company');
let message = notiDoc.getElementById('message');
let close = notiDoc.getElementById('close');
@ -45,7 +45,7 @@ function setStyle(config) {
setStyleOnDomElement(config.defaultStyleTitle, title);
setStyleOnDomElement(config.defaultStylePod, pod);
setStyleOnDomElement(config.defaultStyleCompany, company);
setStyleOnDomElement(config.defaultStyleText, message);
@ -79,7 +79,13 @@ function setContents(event, notificationObj) {
let notiDoc = window.document;
// All the required DOM elements to update the content
let container = notiDoc.getElementById('container');
let titleDoc = notiDoc.getElementById('title');
let companyDoc = notiDoc.getElementById('company');
let messageDoc = notiDoc.getElementById('message');
let imageDoc = notiDoc.getElementById('image');
let closeButton = notiDoc.getElementById('close');
if (notificationObj.color) {
container.style.backgroundColor = notificationObj.color;
@ -88,13 +94,9 @@ function setContents(event, notificationObj) {
if (notificationObj.color.match(whiteColorRegExp)) {
logo.src = './assets/symphony-logo-black.png';
} else {
let title = notiDoc.getElementById('title');
let pod = notiDoc.getElementById('pod');
let message = notiDoc.getElementById('message');
message.style.color = '#ffffff';
title.style.color = '#ffffff';
pod.style.color = notificationObj.color;
messageDoc.style.color = '#ffffff';
titleDoc.style.color = '#ffffff';
companyDoc.style.color = notificationObj.color;
logo.src = './assets/symphony-logo-white.png';
}
}
@ -111,24 +113,26 @@ function setContents(event, notificationObj) {
}
// Title
let titleDoc = notiDoc.getElementById('title');
titleDoc.innerHTML = notificationObj.title || '';
// message
let messageDoc = notiDoc.getElementById('message');
messageDoc.innerHTML = notificationObj.text || '';
// Image
let imageDoc = notiDoc.getElementById('image');
if (notificationObj.image) {
imageDoc.src = notificationObj.image;
} else {
setStyleOnDomElement({ display: 'none'}, imageDoc);
}
const winId = notificationObj.windowId;
// Company
if (notificationObj.company) {
companyDoc.innerHTML = notificationObj.company
} else {
messageDoc.style.marginTop = '15px';
}
let closeButton = notiDoc.getElementById('close');
const winId = notificationObj.windowId;
// note: use onclick because we only want one handler, for case
// when content gets overwritten by notf with same tag

View File

@ -7,7 +7,7 @@
</div>
<div id="header">
<span id="title"></span>
<span id="pod"></span>
<span id="company"></span>
<span id="message"></span>
</div>
<div id="picture">

View File

@ -76,7 +76,7 @@ let config = {
defaultStyleHeader: {
width: 245,
minWidth: 230,
margin: "12px 10px"
margin: "10px 10px"
},
defaultStyleImage: {
height: 43,
@ -102,7 +102,7 @@ let config = {
webkitLineClamp: 1,
webkitBoxOrient: 'vertical',
},
defaultStylePod: {
defaultStyleCompany: {
fontFamily: 'sans-serif',
fontSize: 11,
color: '#adadad',
@ -116,7 +116,7 @@ let config = {
fontFamily: 'sans-serif',
fontSize: 12,
color: '#4a4a4a',
marginTop: 12,
marginTop: 5,
overflow: 'hidden',
display: '-webkit-box',
webkitLineClamp: 1,

View File

@ -41,6 +41,7 @@ class Notify {
color: options.color,
tag: options.tag,
sticky: options.sticky || false,
company: options.company,
onShowFunc: onShow.bind(this),
onClickFunc: onClick.bind(this),
onCloseFunc: onClose.bind(this),

View File

@ -235,12 +235,7 @@ class Search {
* to the main user index
*/
encryptIndex(key) {
return this.crypto.encryption(key).then(() => {
return 'Success'
}).catch((e) => {
log.send(logLevels.ERROR, 'Encrypting the index folder failed ->' + e);
return (new Error(e));
});
return this.crypto.encryption(key);
}
/**

View File

@ -92,7 +92,7 @@ function readFile(userId, resolve, reject) {
}
/**
* If the config as no object for the provided userId this function
* If the config has no object for the provided userId this function
* creates an empty object with the key as the userId
* @param userId
* @param oldConfig
@ -112,7 +112,7 @@ function createUser(userId, oldConfig) {
}
/**
* This function the creates the config
* This function creates the config
* file if not present
* @param userId
* @param data

View File

@ -4,11 +4,13 @@ const isDevEnv = process.env.ELECTRON_DEV ?
process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false;
const isMac = (process.platform === 'darwin');
const isWindowsOS = (process.platform === 'win32');
const isNodeEnv = !!process.env.NODE_ENV;
module.exports = {
isDevEnv: isDevEnv,
isMac: isMac,
isWindowsOS: isWindowsOS,
isNodeEnv: isNodeEnv
};

View File

@ -0,0 +1,120 @@
'use strict';
const { getGlobalConfigField } = require('./../config.js');
const parseDomain = require('parse-domain');
const isEqual = require('lodash.isequal');
/**
* Loops through the list of whitelist urls
* @param url {String} - url the electron is navigated to
* @returns {Promise}
*/
function isWhitelisted(url) {
return new Promise((resolve, reject) => {
getGlobalConfigField('whitelistUrl').then((whitelist) => {
if (checkWhitelist(url, whitelist)) {
return resolve();
}
return reject(new Error('URL does not match with the whitelist'));
}).catch((err) => {
reject(err);
});
});
}
/**
* Method that compares url against a list of whitelist
* returns true if hostName or domain present in the whitelist
* @param url {String} - url the electron is navigated to
* @param whitelist {String} - coma separated whitelists
* @returns {boolean}
*/
function checkWhitelist(url, whitelist) {
let whitelistArray = whitelist.split(',');
const parsedUrl = parseDomain(url);
if (!parsedUrl) {
return false;
}
if (!whitelist) {
return false;
}
if (!whitelistArray.length || whitelistArray.indexOf('*') !== -1) {
return true;
}
return whitelistArray.some((whitelistHost) => {
let parsedWhitelist = parseDomain(whitelistHost);
if (!parsedWhitelist) {
return false;
}
return matchDomains(parsedUrl, parsedWhitelist);
});
}
/**
* Matches the respective hostName
* @param parsedUrl {Object} - parsed url
* @param parsedWhitelist {Object} - parsed whitelist
*
* example:
* matchDomain({ subdomain: www, domain: example, tld: com }, { subdomain: app, domain: example, tld: com })
*
* @returns {*}
*/
function matchDomains(parsedUrl, parsedWhitelist) {
if (isEqual(parsedUrl, parsedWhitelist)) {
return true;
}
const hostNameFromUrl = parsedUrl.domain + parsedUrl.tld;
const hostNameFromWhitelist = parsedWhitelist.domain + parsedWhitelist.tld;
if (!parsedWhitelist.subdomain) {
return hostNameFromUrl === hostNameFromWhitelist
}
return hostNameFromUrl === hostNameFromWhitelist && matchSubDomains(parsedUrl.subdomain, parsedWhitelist.subdomain);
}
/**
* Matches the last occurrence in the sub-domain
* @param subDomainUrl {String} - sub-domain from url
* @param subDomainWhitelist {String} - sub-domain from whitelist
*
* example: matchSubDomains('www', 'app')
*
* @returns {boolean}
*/
function matchSubDomains(subDomainUrl, subDomainWhitelist) {
if (subDomainUrl === subDomainWhitelist) {
return true;
}
const subDomainUrlArray = subDomainUrl.split('.');
const lastCharSubDomainUrl = subDomainUrlArray[subDomainUrlArray.length - 1];
const subDomainWhitelistArray = subDomainWhitelist.split('.');
const lastCharWhitelist = subDomainWhitelistArray[subDomainWhitelistArray.length - 1];
return lastCharSubDomainUrl === lastCharWhitelist;
}
module.exports = {
isWhitelisted,
// items below here are only exported for testing, do NOT use!
checkWhitelist
};

View File

@ -1,5 +1,6 @@
'use strict';
const fs = require('fs');
const electron = require('electron');
const app = electron.app;
const crashReporter = electron.crashReporter;
@ -21,6 +22,7 @@ const throttle = require('./utils/throttle.js');
const { getConfigField, updateConfigField } = require('./config.js');
const { isMac, isNodeEnv } = require('./utils/misc');
const { deleteIndexFolder } = require('./search/search.js');
const { isWhitelisted } = require('./utils/whitelistHandler');
// show dialog when certificate errors occur
require('./dialogs/showCertError.js');
@ -37,6 +39,7 @@ let alwaysOnTop = false;
let position = 'lower-right';
let display;
let sandboxed = false;
let downloadsDirectory;
// note: this file is built using browserify in prebuild step.
const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js');
@ -128,8 +131,8 @@ function doCreateMainWindow(initialUrl, initialBounds) {
newWinOpts.width = bounds.width;
newWinOpts.height = bounds.height;
} else {
newWinOpts.width = 1024;
newWinOpts.height = 768;
newWinOpts.width = 900;
newWinOpts.height = 900;
}
// will center on screen if values not provided
@ -238,11 +241,36 @@ function doCreateMainWindow(initialUrl, initialBounds) {
mainWindow.on('closed', destroyAllWindows);
// if an user has set a custom downloads directory,
// we get that data from the user config file
getConfigField('downloadsDirectory')
.then((value) => {
downloadsDirectory = value;
// if the directory has been deleted, try creating it.
if (!fs.existsSync(downloadsDirectory)) {
const directoryCreated = fs.mkdirSync(downloadsDirectory);
// If the directory creation failed, we use the default downloads directory
if (!directoryCreated) {
downloadsDirectory = null;
}
}
})
.catch((error) => {
log.send(logLevels.ERROR, 'Could not find the downloads directory config -> ' + error);
});
// Manage File Downloads
mainWindow.webContents.session.on('will-download', (event, item, webContents) => {
// When download is in progress, send necessary data to indicate the same
webContents.send('downloadProgress');
// if the user has set a custom downloads directory, save file to that directory
// if otherwise, we save it to the operating system's default downloads directory
if (downloadsDirectory) {
item.setSavePath(downloadsDirectory + "/" + item.getFilename());
}
// Send file path when download is complete
item.once('done', (e, state) => {
if (state === 'completed') {
@ -257,11 +285,6 @@ function doCreateMainWindow(initialUrl, initialBounds) {
});
});
// To delete the user index data folder on navigation
mainWindow.webContents.on('will-navigate', () => {
deleteIndexFolder();
});
getConfigField('url')
.then(initializeCrashReporter)
.catch(app.quit);
@ -353,6 +376,12 @@ function doCreateMainWindow(initialUrl, initialBounds) {
if (browserWin) {
log.send(logLevels.INFO, 'loaded pop-out window url: ' + newWinParsedUrl);
if (!isMac) {
// Removes the menu bar from the pop-out window
// setMenu is currently only supported on Windows and Linux
browserWin.setMenu(null);
}
getConfigField('url')
.then((podUrl) => {
getConfigField('crashReporter')
@ -435,6 +464,21 @@ function doCreateMainWindow(initialUrl, initialBounds) {
}
});
// whenever the main window is navigated for ex: window.location.href or url redirect
mainWindow.webContents.on('will-navigate', function(event, navigatedURL) {
deleteIndexFolder();
isWhitelisted(navigatedURL)
.catch(() => {
event.preventDefault();
electron.dialog.showMessageBox(mainWindow, {
type: 'warning',
buttons: [ 'Ok' ],
title: 'Not Allowed',
message: `Sorry, you are not allowed to access this website (${navigatedURL}), please contact your administrator for more details`,
});
});
});
}
/**
@ -590,6 +634,13 @@ function isAlwaysOnTop(boolean) {
browserWins.forEach(function (browser) {
browser.setAlwaysOnTop(boolean);
});
// An issue where changing the alwaysOnTop property
// focus the pop-out window
// Issue - Electron-209
if (mainWindow && mainWindow.winName) {
activate(mainWindow.winName);
}
}
}
@ -598,6 +649,11 @@ eventEmitter.on('isAlwaysOnTop', (boolean) => {
isAlwaysOnTop(boolean);
});
// set downloads directory
eventEmitter.on('setDownloadsDirectory', (newDirectory) => {
downloadsDirectory = newDirectory;
});
// node event emitter for notification settings
eventEmitter.on('notificationSettings', (notificationSettings) => {
position = notificationSettings.position;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,8 +1,8 @@
{
"name": "Symphony",
"productName": "Symphony",
"version": "2.0.0",
"buildNumber": "",
"version": "2.2.0",
"buildNumber": "124",
"description": "Symphony desktop app (Foundation ODP)",
"author": "Symphony",
"main": "js/main.js",
@ -112,9 +112,11 @@
"ffi": "^2.2.0",
"filesize": "^3.5.10",
"keymirror": "0.1.1",
"lodash.difference": "^4.5.0",
"lodash.isequal": "^4.5.0",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"lodash.difference": "^4.5.0",
"parse-domain": "^2.0.0",
"randomstring": "^1.1.5",
"ref": "^1.3.4",
"shell-path": "^2.1.0",

View File

@ -0,0 +1,74 @@
const { checkWhitelist } = require('../../js/utils/whitelistHandler');
describe('validate url with whitelist', function() {
describe('checkWhitelist truth tests', function() {
it('should return true when the url is valid', function() {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://my.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
it('should return true when if hostName is defined', function() {
const whitelist = 'www.symphony.com, app.symphony.com, symphony.com';
const url = 'https://xyz.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
it('should return true when the first occurrence of sub-domain is matched', function() {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://xyz.my.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
it('should return true when for any URL if whitelist has *', function() {
const whitelist = '*';
const url = 'https://www.example.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
});
describe('checkWhitelist falsity tests', function () {
it('should return false when sub-domain does not match', function () {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://xyz.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false when hostName does not match', function () {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://my.example.com/';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false when the URL is invalid', function () {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'invalidUrl';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false when the whitelist is invalid', function () {
const whitelist = 'invalidWhitelist';
const url = 'https://www.symphony.com';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false if whitelist is empty', function() {
const whitelist = '';
const url = 'https://www.example.com/';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
});
});